@genome-spy/core 0.73.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/AbortablePromiseCache-3gHJdF3E.js +96 -0
- package/dist/bundle/browser-BTgw5ieH.js +126 -0
- package/dist/bundle/chunk-DmhlhrBa.js +11 -0
- package/dist/bundle/esm-BDFRLEuD.js +1248 -0
- package/dist/bundle/esm-BygJiwh0.js +573 -0
- package/dist/bundle/esm-CGX-qz1d.js +155 -0
- package/dist/bundle/esm-CgfVIRJ-.js +121 -0
- package/dist/bundle/esm-CuMSzCHy.js +298 -0
- package/dist/bundle/esm-DMXpJXM4.js +369 -0
- package/dist/bundle/esm-DQiq2Zhd.js +1426 -0
- package/dist/bundle/esm-DtE8VqAv.js +1015 -0
- package/dist/bundle/esm-sIoQYZ21.js +461 -0
- package/dist/bundle/index.es.js +21071 -25935
- package/dist/bundle/index.js +363 -379
- package/dist/bundle/parquetRead-DG_-F5j5.js +1609 -0
- package/dist/schema.json +13098 -7095
- package/dist/src/config/axisConfig.d.ts +16 -0
- package/dist/src/config/axisConfig.d.ts.map +1 -0
- package/dist/src/config/axisConfig.js +84 -0
- package/dist/src/config/defaultConfig.d.ts +3 -0
- package/dist/src/config/defaultConfig.d.ts.map +1 -0
- package/dist/src/config/defaultConfig.js +38 -0
- package/dist/src/config/defaults/axisDefaults.d.ts +5 -0
- package/dist/src/config/defaults/axisDefaults.d.ts.map +1 -0
- package/dist/src/config/defaults/axisDefaults.js +72 -0
- package/dist/src/config/defaults/markDefaults.d.ts +15 -0
- package/dist/src/config/defaults/markDefaults.d.ts.map +1 -0
- package/dist/src/config/defaults/markDefaults.js +121 -0
- package/dist/src/config/defaults/scaleDefaults.d.ts +5 -0
- package/dist/src/config/defaults/scaleDefaults.d.ts.map +1 -0
- package/dist/src/config/defaults/scaleDefaults.js +18 -0
- package/dist/src/config/defaults/titleDefaults.d.ts +5 -0
- package/dist/src/config/defaults/titleDefaults.d.ts.map +1 -0
- package/dist/src/config/defaults/titleDefaults.js +47 -0
- package/dist/src/config/defaults/viewDefaults.d.ts +3 -0
- package/dist/src/config/defaults/viewDefaults.d.ts.map +1 -0
- package/dist/src/config/defaults/viewDefaults.js +2 -0
- package/dist/src/config/markConfig.d.ts +8 -0
- package/dist/src/config/markConfig.d.ts.map +1 -0
- package/dist/src/config/markConfig.js +27 -0
- package/dist/src/config/mergeConfig.d.ts +8 -0
- package/dist/src/config/mergeConfig.d.ts.map +1 -0
- package/dist/src/config/mergeConfig.js +81 -0
- package/dist/src/config/resolveConfig.d.ts +22 -0
- package/dist/src/config/resolveConfig.d.ts.map +1 -0
- package/dist/src/config/resolveConfig.js +32 -0
- package/dist/src/config/scaleConfig.d.ts +40 -0
- package/dist/src/config/scaleConfig.d.ts.map +1 -0
- package/dist/src/config/scaleConfig.js +220 -0
- package/dist/src/config/styleUtils.d.ts +6 -0
- package/dist/src/config/styleUtils.d.ts.map +1 -0
- package/dist/src/config/styleUtils.js +10 -0
- package/dist/src/config/themes.d.ts +15 -0
- package/dist/src/config/themes.d.ts.map +1 -0
- package/dist/src/config/themes.js +293 -0
- package/dist/src/config/titleConfig.d.ts +12 -0
- package/dist/src/config/titleConfig.d.ts.map +1 -0
- package/dist/src/config/titleConfig.js +42 -0
- package/dist/src/config/viewConfig.d.ts +7 -0
- package/dist/src/config/viewConfig.d.ts.map +1 -0
- package/dist/src/config/viewConfig.js +29 -0
- package/dist/src/data/flowNode.d.ts +22 -1
- package/dist/src/data/flowNode.d.ts.map +1 -1
- package/dist/src/data/flowNode.js +37 -1
- package/dist/src/data/formats/bed.d.ts.map +1 -1
- package/dist/src/data/formats/bed.js +6 -1
- package/dist/src/data/formats/bedpe.d.ts.map +1 -1
- package/dist/src/data/formats/bedpe.js +4 -0
- package/dist/src/data/formats/fasta.d.ts.map +1 -1
- package/dist/src/data/formats/fasta.js +4 -0
- package/dist/src/data/formats/parquet.d.ts.map +1 -1
- package/dist/src/data/formats/parquet.js +4 -0
- package/dist/src/data/sources/dataSourceFactory.d.ts +2 -13
- package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
- package/dist/src/data/sources/dataSourceFactory.js +5 -141
- package/dist/src/data/sources/lazy/axisGenomeSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/axisGenomeSource.js +11 -0
- package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
- package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/axisTickSource.js +19 -8
- package/dist/src/data/sources/lazy/bamSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bamSource.js +11 -0
- package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.js +12 -1
- package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigWigSource.js +11 -0
- package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/gff3Source.js +12 -1
- package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/indexedFastaSource.js +11 -0
- package/dist/src/data/sources/lazy/lazyDataSourceRegistry.d.ts +27 -0
- package/dist/src/data/sources/lazy/lazyDataSourceRegistry.d.ts.map +1 -0
- package/dist/src/data/sources/lazy/lazyDataSourceRegistry.js +65 -0
- package/dist/src/data/sources/lazy/registerBuiltInLazySources.d.ts +2 -0
- package/dist/src/data/sources/lazy/registerBuiltInLazySources.d.ts.map +1 -0
- package/dist/src/data/sources/lazy/registerBuiltInLazySources.js +8 -0
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +11 -2
- package/dist/src/data/sources/lazy/vcfSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/vcfSource.js +11 -0
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +5 -2
- package/dist/src/data/transforms/aggregate.d.ts +1 -0
- package/dist/src/data/transforms/aggregate.d.ts.map +1 -1
- package/dist/src/data/transforms/aggregate.js +30 -8
- package/dist/src/data/transforms/aggregateOps.d.ts.map +1 -1
- package/dist/src/data/transforms/aggregateOps.js +12 -1
- package/dist/src/data/transforms/coverage.js +2 -2
- package/dist/src/data/transforms/filter.js +1 -1
- package/dist/src/data/transforms/filterScoredLabels.d.ts +6 -0
- package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
- package/dist/src/data/transforms/filterScoredLabels.js +9 -0
- package/dist/src/data/transforms/measureText.d.ts +1 -0
- package/dist/src/data/transforms/measureText.d.ts.map +1 -1
- package/dist/src/data/transforms/measureText.js +14 -5
- package/dist/src/data/transforms/pileup.d.ts.map +1 -1
- package/dist/src/data/transforms/pileup.js +1 -2
- package/dist/src/data/transforms/regexFold.d.ts.map +1 -1
- package/dist/src/data/transforms/regexFold.js +0 -1
- package/dist/src/embedFactory.d.ts +13 -0
- package/dist/src/embedFactory.d.ts.map +1 -0
- package/dist/src/embedFactory.js +127 -0
- package/dist/src/encoder/accessor.d.ts +3 -12
- package/dist/src/encoder/accessor.d.ts.map +1 -1
- package/dist/src/encoder/accessor.js +10 -65
- package/dist/src/encoder/encoder.d.ts +51 -8
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +179 -55
- package/dist/src/fonts/bmFontManager.js +1 -1
- package/dist/src/full.d.ts +2 -0
- package/dist/src/full.d.ts.map +1 -0
- package/dist/src/full.js +2 -0
- package/dist/src/genome/genome.d.ts +8 -0
- package/dist/src/genome/genome.d.ts.map +1 -1
- package/dist/src/genome/genome.js +16 -2
- package/dist/src/genome/genomeStore.js +1 -1
- package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -1
- package/dist/src/genome/rootGenomeConfig.js +6 -2
- package/dist/src/genome/scaleLocus.d.ts.map +1 -1
- package/dist/src/genome/scaleLocus.js +26 -7
- package/dist/src/genomeSpy/cursorManager.d.ts +69 -0
- package/dist/src/genomeSpy/cursorManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/cursorManager.js +131 -0
- package/dist/src/genomeSpy/headlessBootstrap.d.ts +113 -0
- package/dist/src/genomeSpy/headlessBootstrap.d.ts.map +1 -0
- package/dist/src/genomeSpy/headlessBootstrap.js +246 -0
- package/dist/src/genomeSpy/interactionController.d.ts +5 -0
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
- package/dist/src/genomeSpy/interactionController.js +212 -19
- package/dist/src/genomeSpy/interactionDispatcher.d.ts +50 -0
- package/dist/src/genomeSpy/interactionDispatcher.d.ts.map +1 -0
- package/dist/src/genomeSpy/interactionDispatcher.js +203 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts +4 -2
- package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewContextFactory.js +12 -4
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewDataInit.js +7 -3
- package/dist/src/genomeSpy.d.ts +1 -124
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +7 -688
- package/dist/src/genomeSpyBase.d.ts +133 -0
- package/dist/src/genomeSpyBase.d.ts.map +1 -0
- package/dist/src/genomeSpyBase.js +719 -0
- package/dist/src/gl/arrayBuilder.d.ts.map +1 -1
- package/dist/src/gl/arrayBuilder.js +0 -3
- package/dist/src/gl/colorUtils.d.ts.map +1 -1
- package/dist/src/gl/colorUtils.js +3 -0
- package/dist/src/gl/dataToVertices.d.ts.map +1 -1
- package/dist/src/gl/dataToVertices.js +13 -8
- package/dist/src/gl/glslScaleGenerator.d.ts +2 -2
- package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
- package/dist/src/gl/glslScaleGenerator.js +5 -7
- package/dist/src/index.d.ts +3 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -114
- package/dist/src/marks/__snapshots__/shaderSnapshot.test.js.snap +4462 -0
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +0 -23
- package/dist/src/marks/mark.d.ts +8 -1
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +61 -20
- package/dist/src/marks/markUtils.d.ts +18 -1
- package/dist/src/marks/markUtils.d.ts.map +1 -1
- package/dist/src/marks/markUtils.js +52 -4
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.js +6 -26
- package/dist/src/marks/rect.d.ts.map +1 -1
- package/dist/src/marks/rect.js +13 -21
- package/dist/src/marks/rule.d.ts +7 -2
- package/dist/src/marks/rule.d.ts.map +1 -1
- package/dist/src/marks/rule.js +125 -16
- package/dist/src/marks/text.d.ts.map +1 -1
- package/dist/src/marks/text.js +5 -47
- package/dist/src/minimal.d.ts +8 -0
- package/dist/src/minimal.d.ts.map +1 -0
- package/dist/src/minimal.js +21 -0
- package/dist/src/paramRuntime/viewParamRuntime.d.ts +19 -0
- package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -1
- package/dist/src/paramRuntime/viewParamRuntime.js +35 -0
- package/dist/src/scale/scale.d.ts.map +1 -1
- package/dist/src/scale/scale.js +13 -7
- package/dist/src/scales/axisResolution.d.ts.map +1 -1
- package/dist/src/scales/axisResolution.js +9 -5
- package/dist/src/scales/domainPlanner.d.ts +28 -8
- package/dist/src/scales/domainPlanner.d.ts.map +1 -1
- package/dist/src/scales/domainPlanner.js +207 -73
- package/dist/src/scales/indexLikeDomainUtils.d.ts +29 -0
- package/dist/src/scales/indexLikeDomainUtils.d.ts.map +1 -0
- package/dist/src/scales/indexLikeDomainUtils.js +67 -0
- package/dist/src/scales/resolutionMemberOrder.d.ts +15 -0
- package/dist/src/scales/resolutionMemberOrder.d.ts.map +1 -0
- package/dist/src/scales/resolutionMemberOrder.js +22 -0
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
- package/dist/src/scales/scaleInteractionController.js +43 -4
- package/dist/src/scales/scalePropsResolver.d.ts +3 -1
- package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
- package/dist/src/scales/scalePropsResolver.js +83 -6
- package/dist/src/scales/scaleResolution.d.ts +23 -0
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +220 -58
- package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -1
- package/dist/src/scales/scaleResolutionTestUtils.js +6 -2
- package/dist/src/scales/scaleRules.d.ts.map +1 -1
- package/dist/src/scales/scaleRules.js +16 -2
- package/dist/src/scales/selectionDomainUtils.d.ts +30 -0
- package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -1
- package/dist/src/scales/selectionDomainUtils.js +116 -1
- package/dist/src/screenshotHarness.js +3 -4
- package/dist/src/spec/axis.d.ts +41 -30
- package/dist/src/spec/channel.d.ts +15 -9
- package/dist/src/spec/config.d.ts +264 -0
- package/dist/src/spec/data.d.ts +7 -0
- package/dist/src/spec/decoration.d.ts +51 -0
- package/dist/src/spec/exampleFiles.d.ts +12 -0
- package/dist/src/spec/exampleFiles.d.ts.map +1 -0
- package/dist/src/spec/exampleFiles.js +52 -0
- package/dist/src/spec/font.d.ts +1 -1
- package/dist/src/spec/mark.d.ts +97 -13
- package/dist/src/spec/parameter.d.ts +30 -10
- package/dist/src/spec/root.d.ts +14 -0
- package/dist/src/spec/scale.d.ts +18 -13
- package/dist/src/spec/title.d.ts +13 -2
- package/dist/src/spec/tooltip.d.ts +1 -1
- package/dist/src/spec/transform.d.ts +39 -4
- package/dist/src/spec/view.d.ts +67 -19
- package/dist/src/styles/genome-spy.css +55 -55
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +23 -22
- package/dist/src/testSetup.d.ts +2 -0
- package/dist/src/testSetup.d.ts.map +1 -0
- package/dist/src/testSetup.js +5 -0
- package/dist/src/tooltip/dataTooltipHandler.js +8 -2
- package/dist/src/tooltip/tooltipContext.d.ts.map +1 -1
- package/dist/src/tooltip/tooltipContext.js +3 -2
- package/dist/src/types/embedApi.d.ts +7 -0
- package/dist/src/types/encoder.d.ts +17 -15
- package/dist/src/types/scaleResolutionApi.d.ts +20 -0
- package/dist/src/types/viewContext.d.ts +23 -1
- package/dist/src/utils/expression.d.ts +2 -2
- package/dist/src/utils/expression.d.ts.map +1 -1
- package/dist/src/utils/expression.js +63 -8
- package/dist/src/utils/field.d.ts.map +1 -1
- package/dist/src/utils/field.js +0 -1
- package/dist/src/utils/inertia.d.ts.map +1 -1
- package/dist/src/utils/inertia.js +0 -1
- package/dist/src/utils/inputBinding.d.ts +1 -1
- package/dist/src/utils/interaction.d.ts +109 -0
- package/dist/src/utils/interaction.d.ts.map +1 -0
- package/dist/src/utils/interaction.js +200 -0
- package/dist/src/utils/interactionEvent.d.ts +21 -42
- package/dist/src/utils/interactionEvent.d.ts.map +1 -1
- package/dist/src/utils/interactionEvent.js +43 -66
- package/dist/src/utils/kWayMerge.js +1 -1
- package/dist/src/utils/mergeObjects.d.ts.map +1 -1
- package/dist/src/utils/mergeObjects.js +0 -2
- package/dist/src/utils/radixSort.d.ts.map +1 -1
- package/dist/src/utils/radixSort.js +0 -2
- package/dist/src/utils/throttle.d.ts.map +1 -1
- package/dist/src/utils/throttle.js +0 -2
- package/dist/src/utils/ui/tooltip.d.ts +1 -0
- package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
- package/dist/src/utils/ui/tooltip.js +1 -0
- package/dist/src/utils/url.js +1 -1
- package/dist/src/view/axisGridView.d.ts +1 -1
- package/dist/src/view/axisGridView.d.ts.map +1 -1
- package/dist/src/view/axisGridView.js +2 -47
- package/dist/src/view/axisView.d.ts +2 -3
- package/dist/src/view/axisView.d.ts.map +1 -1
- package/dist/src/view/axisView.js +251 -106
- package/dist/src/view/concatView.d.ts +2 -1
- package/dist/src/view/concatView.d.ts.map +1 -1
- package/dist/src/view/concatView.js +4 -2
- package/dist/src/view/containerMutationHelper.d.ts +3 -0
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
- package/dist/src/view/containerMutationHelper.js +4 -1
- package/dist/src/view/facetView.d.ts +1 -1
- package/dist/src/view/facetView.js +3 -3
- package/dist/src/view/flowBuilder.js +2 -2
- package/dist/src/view/gridView/gridChild.d.ts +6 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +72 -43
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +255 -101
- package/dist/src/view/gridView/keyboardZoomController.d.ts +2 -2
- package/dist/src/view/gridView/keyboardZoomController.d.ts.map +1 -1
- package/dist/src/view/gridView/keyboardZoomController.js +1 -1
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
- package/dist/src/view/gridView/scrollbar.js +4 -2
- package/dist/src/view/gridView/selectionRect.d.ts +4 -0
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +20 -1
- package/dist/src/view/gridView/separatorView.d.ts +1 -0
- package/dist/src/view/gridView/separatorView.d.ts.map +1 -1
- package/dist/src/view/gridView/separatorView.js +9 -0
- package/dist/src/view/interactionRouting.d.ts +20 -0
- package/dist/src/view/interactionRouting.d.ts.map +1 -0
- package/dist/src/view/interactionRouting.js +53 -0
- package/dist/src/view/layerView.d.ts.map +1 -1
- package/dist/src/view/layerView.js +12 -9
- package/dist/src/view/layout/grid.js +1 -1
- package/dist/src/view/renderingContext/bufferedViewRenderingContext.d.ts.map +1 -1
- package/dist/src/view/renderingContext/bufferedViewRenderingContext.js +0 -2
- package/dist/src/view/testUtils.d.ts +17 -3
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +62 -69
- package/dist/src/view/title.d.ts +8 -1
- package/dist/src/view/title.d.ts.map +1 -1
- package/dist/src/view/title.js +66 -76
- package/dist/src/view/unitView.d.ts +1 -1
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +67 -17
- package/dist/src/view/view.d.ts +76 -30
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +136 -47
- package/dist/src/view/viewFactory.d.ts +11 -3
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +37 -11
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +41 -5
- package/dist/src/view/zoom.d.ts +2 -2
- package/dist/src/view/zoom.d.ts.map +1 -1
- package/dist/src/view/zoom.js +21 -23
- package/package.json +18 -10
- package/dist/bundle/AbortablePromiseCache-Dj0vzLnp.js +0 -149
- package/dist/bundle/browser-0iNU5Wit.js +0 -138
- package/dist/bundle/index-BYsZN7b0.js +0 -1597
- package/dist/bundle/index-C3kClAEN.js +0 -1771
- package/dist/bundle/index-C7wOh6y1.js +0 -657
- package/dist/bundle/index-CRaQAuki.js +0 -326
- package/dist/bundle/index-D9v1PCj9.js +0 -507
- package/dist/bundle/index-GDOuv_D5.js +0 -266
- package/dist/bundle/index-Gt44EOIH.js +0 -628
- package/dist/bundle/inflate-GtwLkvSP.js +0 -1048
- package/dist/bundle/parquetRead-BnAGCa4_.js +0 -1663
- package/dist/bundle/unzip-Bac01w6X.js +0 -1492
- package/dist/src/config/scaleDefaults.d.ts +0 -8
- package/dist/src/config/scaleDefaults.d.ts.map +0 -1
- package/dist/src/config/scaleDefaults.js +0 -45
|
@@ -0,0 +1,4462 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`generated shader snapshots > interval selection example 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"fragment": "precision highp float;
|
|
6
|
+
precision highp int;
|
|
7
|
+
|
|
8
|
+
// view: viewRoot
|
|
9
|
+
|
|
10
|
+
layout(std140) uniform Mark {
|
|
11
|
+
/**
|
|
12
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
13
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
14
|
+
* geometric zoom, etc.
|
|
15
|
+
*/
|
|
16
|
+
uniform bool uInwardStroke;
|
|
17
|
+
|
|
18
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
19
|
+
uniform float uMinPickingSize;
|
|
20
|
+
|
|
21
|
+
/** Scale factor for geometric zoom */
|
|
22
|
+
uniform mediump float uScaleFactor;
|
|
23
|
+
|
|
24
|
+
uniform mediump float uZoomLevel;
|
|
25
|
+
uniform highp float uSemanticThreshold;
|
|
26
|
+
|
|
27
|
+
uniform mediump float uGradientStrength;
|
|
28
|
+
|
|
29
|
+
// Selection parameter
|
|
30
|
+
uniform highp float[2] uParam_brush_x;
|
|
31
|
+
mediump float uDomain_x[2];
|
|
32
|
+
|
|
33
|
+
mediump float uDomain_y[2];
|
|
34
|
+
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
#define PI 3.141593
|
|
39
|
+
|
|
40
|
+
uniform View {
|
|
41
|
+
/** Offset in "unit" units */
|
|
42
|
+
mediump vec2 uViewOffset;
|
|
43
|
+
mediump vec2 uViewScale;
|
|
44
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
45
|
+
mediump vec2 uViewportSize;
|
|
46
|
+
lowp float uDevicePixelRatio;
|
|
47
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
48
|
+
// that is rendered with the specified opacity.
|
|
49
|
+
lowp float uViewOpacity;
|
|
50
|
+
bool uPickingEnabled;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
56
|
+
* (0, 0) is at the bottom left corner.
|
|
57
|
+
*/
|
|
58
|
+
vec4 unitToNdc(vec2 coord) {
|
|
59
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
vec4 unitToNdc(float x, float y) {
|
|
63
|
+
return unitToNdc(vec2(x, y));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
67
|
+
return unitToNdc(coord / uViewportSize);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
71
|
+
return pixelsToNdc(vec2(x, y));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
75
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
79
|
+
|
|
80
|
+
highp uint hash32(highp uint key) {
|
|
81
|
+
highp uint v = key;
|
|
82
|
+
v ^= v >> 16u;
|
|
83
|
+
v *= 0x7feb352du;
|
|
84
|
+
v ^= v >> 15u;
|
|
85
|
+
v *= 0x846ca68bu;
|
|
86
|
+
v ^= v >> 16u;
|
|
87
|
+
return v;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
91
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
92
|
+
ivec2 texSize = textureSize(s, 0);
|
|
93
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
97
|
+
ivec2 texSize = textureSize(s, 0);
|
|
98
|
+
highp uint width = uint(texSize.x);
|
|
99
|
+
highp uint size = width * uint(texSize.y);
|
|
100
|
+
highp uint mask = size - 1u;
|
|
101
|
+
highp uint index = hash32(value) & mask;
|
|
102
|
+
|
|
103
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
104
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
105
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
106
|
+
if (entry == value) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
index = (index + 1u) & mask;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
120
|
+
*/
|
|
121
|
+
float getGammaForColor(vec3 rgb) {
|
|
122
|
+
return mix(
|
|
123
|
+
1.25,
|
|
124
|
+
0.75,
|
|
125
|
+
// RGB should be linearized but this is good enough for now
|
|
126
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
130
|
+
|
|
131
|
+
// TODO: include the following only in fragment shaders
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Specialized linearstep for doing antialiasing
|
|
135
|
+
*/
|
|
136
|
+
float distanceToRatio(float d) {
|
|
137
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
141
|
+
if (halfStrokeWidth > 0.0) {
|
|
142
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
143
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
144
|
+
return mix(
|
|
145
|
+
stroke,
|
|
146
|
+
d <= 0.0 ? fill : background,
|
|
147
|
+
distanceToRatio(sd));
|
|
148
|
+
} else {
|
|
149
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
in highp vec4 vPickingColor;
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
const lowp vec4 white = vec4(1.0);
|
|
158
|
+
const lowp vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
|
|
159
|
+
|
|
160
|
+
flat in float vRadius;
|
|
161
|
+
flat in float vRadiusWithPadding;
|
|
162
|
+
|
|
163
|
+
flat in lowp vec4 vFillColor;
|
|
164
|
+
flat in lowp vec4 vStrokeColor;
|
|
165
|
+
flat in lowp float vShape;
|
|
166
|
+
flat in lowp float vHalfStrokeWidth;
|
|
167
|
+
|
|
168
|
+
flat in mat2 vRotationMatrix;
|
|
169
|
+
|
|
170
|
+
out lowp vec4 fragColor;
|
|
171
|
+
|
|
172
|
+
// Copypaste from vertex shader
|
|
173
|
+
const float CIRCLE = 0.0;
|
|
174
|
+
const float SQUARE = 1.0;
|
|
175
|
+
const float CROSS = 2.0;
|
|
176
|
+
const float DIAMOND = 3.0;
|
|
177
|
+
const float TRIANGLE_UP = 4.0;
|
|
178
|
+
const float TICK_UP = 8.0;
|
|
179
|
+
|
|
180
|
+
// The distance functions are inspired by:
|
|
181
|
+
// http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
|
182
|
+
// However, these are not true distance functions, because the corners need to be sharp.
|
|
183
|
+
|
|
184
|
+
float circle(vec2 p, float r) {
|
|
185
|
+
return length(p) - r;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
float square(vec2 p, float r) {
|
|
189
|
+
p = abs(p);
|
|
190
|
+
return max(p.x, p.y) - r;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
float tickUp(vec2 p, float r) {
|
|
194
|
+
float halfR = r * 0.5;
|
|
195
|
+
p.y += halfR;
|
|
196
|
+
p = abs(p);
|
|
197
|
+
return max(p.x - r * 0.15, p.y - halfR);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
float equilateralTriangle(vec2 p, float r) {
|
|
201
|
+
p.y = -p.y;
|
|
202
|
+
float k = sqrt(3.0);
|
|
203
|
+
float kr = k * r;
|
|
204
|
+
p.y -= kr / 2.0;
|
|
205
|
+
return max((abs(p.x) * k + p.y) / 2.0, -p.y - kr);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
float crossShape(vec2 p, float r) {
|
|
209
|
+
p = abs(p);
|
|
210
|
+
|
|
211
|
+
vec2 b = vec2(0.4, 1.0) * r;
|
|
212
|
+
vec2 v = abs(p) - b.xy;
|
|
213
|
+
vec2 h = abs(p) - b.yx;
|
|
214
|
+
return min(max(v.x, v.y), max(h.x, h.y));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
float diamond(vec2 p, float r) {
|
|
218
|
+
p = abs(p);
|
|
219
|
+
return (max(abs(p.x - p.y), abs(p.x + p.y)) - r) / sqrt(2.0);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
void main() {
|
|
223
|
+
float d;
|
|
224
|
+
|
|
225
|
+
/** Normalized point coord */
|
|
226
|
+
vec2 p = vRotationMatrix * (2.0 * gl_PointCoord - 1.0) * vRadiusWithPadding;
|
|
227
|
+
float r = vRadius;
|
|
228
|
+
|
|
229
|
+
// We could also use textures here. Could even be faster, because we have plenty of branching here.
|
|
230
|
+
if (vShape == CIRCLE) {
|
|
231
|
+
d = circle(p, r);
|
|
232
|
+
|
|
233
|
+
} else if (vShape == SQUARE) {
|
|
234
|
+
d = square(p, r);
|
|
235
|
+
|
|
236
|
+
} else if (vShape == CROSS) {
|
|
237
|
+
d = crossShape(p, r);
|
|
238
|
+
|
|
239
|
+
} else if (vShape == DIAMOND) {
|
|
240
|
+
d = diamond(p, r);
|
|
241
|
+
|
|
242
|
+
} else if (vShape == TRIANGLE_UP) {
|
|
243
|
+
d = equilateralTriangle(p, r);
|
|
244
|
+
|
|
245
|
+
} else if (vShape == TICK_UP) {
|
|
246
|
+
d = tickUp(p, r);
|
|
247
|
+
|
|
248
|
+
} else {
|
|
249
|
+
d = 0.0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!uPickingEnabled) {
|
|
253
|
+
lowp vec4 fillColor = mix(vFillColor, white, -d * uGradientStrength / vRadius);
|
|
254
|
+
|
|
255
|
+
fragColor = distanceToColor(
|
|
256
|
+
d + (uInwardStroke ? vHalfStrokeWidth : 0.0),
|
|
257
|
+
fillColor,
|
|
258
|
+
vStrokeColor,
|
|
259
|
+
vec4(0.0),
|
|
260
|
+
vHalfStrokeWidth);
|
|
261
|
+
|
|
262
|
+
} else if (d - vHalfStrokeWidth <= 0.0) {
|
|
263
|
+
fragColor = vPickingColor;
|
|
264
|
+
|
|
265
|
+
} else {
|
|
266
|
+
discard;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
",
|
|
271
|
+
"vertex": "precision highp float;
|
|
272
|
+
precision highp int;
|
|
273
|
+
|
|
274
|
+
// view: viewRoot
|
|
275
|
+
|
|
276
|
+
layout(std140) uniform Mark {
|
|
277
|
+
/**
|
|
278
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
279
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
280
|
+
* geometric zoom, etc.
|
|
281
|
+
*/
|
|
282
|
+
uniform bool uInwardStroke;
|
|
283
|
+
|
|
284
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
285
|
+
uniform float uMinPickingSize;
|
|
286
|
+
|
|
287
|
+
/** Scale factor for geometric zoom */
|
|
288
|
+
uniform mediump float uScaleFactor;
|
|
289
|
+
|
|
290
|
+
uniform mediump float uZoomLevel;
|
|
291
|
+
uniform highp float uSemanticThreshold;
|
|
292
|
+
|
|
293
|
+
uniform mediump float uGradientStrength;
|
|
294
|
+
|
|
295
|
+
// Selection parameter
|
|
296
|
+
uniform highp float[2] uParam_brush_x;
|
|
297
|
+
mediump float uDomain_x[2];
|
|
298
|
+
|
|
299
|
+
mediump float uDomain_y[2];
|
|
300
|
+
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
#define PI 3.141593
|
|
305
|
+
|
|
306
|
+
uniform View {
|
|
307
|
+
/** Offset in "unit" units */
|
|
308
|
+
mediump vec2 uViewOffset;
|
|
309
|
+
mediump vec2 uViewScale;
|
|
310
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
311
|
+
mediump vec2 uViewportSize;
|
|
312
|
+
lowp float uDevicePixelRatio;
|
|
313
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
314
|
+
// that is rendered with the specified opacity.
|
|
315
|
+
lowp float uViewOpacity;
|
|
316
|
+
bool uPickingEnabled;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
322
|
+
* (0, 0) is at the bottom left corner.
|
|
323
|
+
*/
|
|
324
|
+
vec4 unitToNdc(vec2 coord) {
|
|
325
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
vec4 unitToNdc(float x, float y) {
|
|
329
|
+
return unitToNdc(vec2(x, y));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
333
|
+
return unitToNdc(coord / uViewportSize);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
337
|
+
return pixelsToNdc(vec2(x, y));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
341
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
345
|
+
|
|
346
|
+
highp uint hash32(highp uint key) {
|
|
347
|
+
highp uint v = key;
|
|
348
|
+
v ^= v >> 16u;
|
|
349
|
+
v *= 0x7feb352du;
|
|
350
|
+
v ^= v >> 15u;
|
|
351
|
+
v *= 0x846ca68bu;
|
|
352
|
+
v ^= v >> 16u;
|
|
353
|
+
return v;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
357
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
358
|
+
ivec2 texSize = textureSize(s, 0);
|
|
359
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
363
|
+
ivec2 texSize = textureSize(s, 0);
|
|
364
|
+
highp uint width = uint(texSize.x);
|
|
365
|
+
highp uint size = width * uint(texSize.y);
|
|
366
|
+
highp uint mask = size - 1u;
|
|
367
|
+
highp uint index = hash32(value) & mask;
|
|
368
|
+
|
|
369
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
370
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
371
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
372
|
+
if (entry == value) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
index = (index + 1u) & mask;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
386
|
+
*/
|
|
387
|
+
float getGammaForColor(vec3 rgb) {
|
|
388
|
+
return mix(
|
|
389
|
+
1.25,
|
|
390
|
+
0.75,
|
|
391
|
+
// RGB should be linearized but this is good enough for now
|
|
392
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
396
|
+
|
|
397
|
+
// TODO: include the following only in fragment shaders
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Specialized linearstep for doing antialiasing
|
|
401
|
+
*/
|
|
402
|
+
float distanceToRatio(float d) {
|
|
403
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
407
|
+
if (halfStrokeWidth > 0.0) {
|
|
408
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
409
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
410
|
+
return mix(
|
|
411
|
+
stroke,
|
|
412
|
+
d <= 0.0 ? fill : background,
|
|
413
|
+
distanceToRatio(sd));
|
|
414
|
+
} else {
|
|
415
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
uniform highp float uZero;
|
|
421
|
+
|
|
422
|
+
// Utils ------------
|
|
423
|
+
|
|
424
|
+
vec3 getDiscreteColor(sampler2D s, int index) {
|
|
425
|
+
return texelFetch(s, ivec2(index % textureSize(s, 0).x, 0), 0).rgb;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
vec3 getInterpolatedColor(sampler2D s, float unitValue) {
|
|
429
|
+
return texture(s, vec2(unitValue, 0.0)).rgb;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
float clampToRange(float value, vec2 range) {
|
|
433
|
+
return clamp(value, min(range[0], range[1]), max(range[0], range[1]));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Scales ------------
|
|
437
|
+
// Based on d3 scales: https://github.com/d3/d3-scale
|
|
438
|
+
|
|
439
|
+
float scaleIdentity(float value) {
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
float scaleIdentity(uint value) {
|
|
444
|
+
return float(value);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
float scaleLinear(float value, vec2 domain, vec2 range) {
|
|
448
|
+
float domainSpan = domain[1] - domain[0];
|
|
449
|
+
float rangeSpan = range[1] - range[0];
|
|
450
|
+
return (value - domain[0]) / domainSpan * rangeSpan + range[0];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
float scaleLog(float value, vec2 domain, vec2 range, float base) {
|
|
454
|
+
// y = m log(x) + b
|
|
455
|
+
// TODO: Perf optimization: precalculate log domain in js.
|
|
456
|
+
// TODO: Reversed domain, etc
|
|
457
|
+
return scaleLinear(log(value) / log(base), log(domain) / log(base), range);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
float symlog(float value, float constant) {
|
|
461
|
+
// WARNING: emulating log1p with log(x + 1). Small numbers are likely to
|
|
462
|
+
// have significant precision problems.
|
|
463
|
+
return sign(value) * log(abs(value / constant) + 1.0);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
float scaleSymlog(float value, vec2 domain, vec2 range, float constant) {
|
|
467
|
+
return scaleLinear(
|
|
468
|
+
symlog(value, constant),
|
|
469
|
+
vec2(symlog(domain[0], constant), symlog(domain[1], constant)),
|
|
470
|
+
range
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
float scalePow(float value, vec2 domain, vec2 range, float exponent) {
|
|
475
|
+
// y = mx^k + b
|
|
476
|
+
// TODO: Perf optimization: precalculate pow domain in js.
|
|
477
|
+
// TODO: Reversed domain, etc
|
|
478
|
+
return scaleLinear(
|
|
479
|
+
pow(abs(value), exponent) * sign(value),
|
|
480
|
+
pow(abs(domain), vec2(exponent)) * sign(domain),
|
|
481
|
+
range
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// TODO: scaleThreshold
|
|
486
|
+
// TODO: scaleQuantile (special case of threshold scale)
|
|
487
|
+
|
|
488
|
+
// TODO: domainExtent should be uint
|
|
489
|
+
float scaleBand(uint value, vec2 domainExtent, vec2 range,
|
|
490
|
+
float paddingInner, float paddingOuter,
|
|
491
|
+
float align, float band) {
|
|
492
|
+
|
|
493
|
+
// TODO: reverse
|
|
494
|
+
float start = range[0];
|
|
495
|
+
float stop = range[1];
|
|
496
|
+
float rangeSpan = stop - start;
|
|
497
|
+
|
|
498
|
+
float n = domainExtent[1] - domainExtent[0];
|
|
499
|
+
|
|
500
|
+
// This fix departs from Vega and d3: https://github.com/vega/vega/issues/3357#issuecomment-1063253596
|
|
501
|
+
paddingInner = int(n) > 1 ? paddingInner : 0.0;
|
|
502
|
+
|
|
503
|
+
// Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
|
|
504
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
505
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
506
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
507
|
+
|
|
508
|
+
return start + (float(value) - domainExtent[0]) * step + bandwidth * band;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const int lowBits = 12;
|
|
512
|
+
const float lowDivisor = pow(2.0, float(lowBits));
|
|
513
|
+
const uint lowMask = uint(lowDivisor - 1.0);
|
|
514
|
+
|
|
515
|
+
vec2 splitUint(uint value) {
|
|
516
|
+
uint valueLo = value & lowMask;
|
|
517
|
+
uint valueHi = value - valueLo;
|
|
518
|
+
return vec2(float(valueHi), float(valueLo));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* High precision variant of scaleBand for index/locus scales
|
|
523
|
+
*/
|
|
524
|
+
float scaleBandHp(uint value, vec3 domainExtent, vec2 range,
|
|
525
|
+
float paddingInner, float paddingOuter,
|
|
526
|
+
float align, float band) {
|
|
527
|
+
|
|
528
|
+
// TODO: reverse
|
|
529
|
+
float start = range[0];
|
|
530
|
+
float stop = range[1];
|
|
531
|
+
float rangeSpan = stop - start;
|
|
532
|
+
|
|
533
|
+
vec2 domainStart = domainExtent.xy;
|
|
534
|
+
float n = domainExtent[2];
|
|
535
|
+
|
|
536
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
537
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
538
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
539
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
540
|
+
|
|
541
|
+
// Split into to values with each having a reduced number of significant digits
|
|
542
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
543
|
+
vec2 splitValue = splitUint(value);
|
|
544
|
+
|
|
545
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
546
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
547
|
+
// some equivalent form that does premature rounding.
|
|
548
|
+
float inf = 1.0 / uZero;
|
|
549
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
550
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
551
|
+
|
|
552
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* High precision variant of scaleBand for index/locus scales for large
|
|
557
|
+
* domains where 32bit uints are not sufficient to represent the domain.
|
|
558
|
+
*/
|
|
559
|
+
float scaleBandHp(uvec2 value, vec3 domainExtent, vec2 range,
|
|
560
|
+
float paddingInner, float paddingOuter,
|
|
561
|
+
float align, float band) {
|
|
562
|
+
|
|
563
|
+
// TODO: reverse
|
|
564
|
+
float start = range[0];
|
|
565
|
+
float stop = range[1];
|
|
566
|
+
float rangeSpan = stop - start;
|
|
567
|
+
|
|
568
|
+
vec2 domainStart = domainExtent.xy;
|
|
569
|
+
float n = domainExtent[2];
|
|
570
|
+
|
|
571
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
572
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
573
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
574
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
575
|
+
|
|
576
|
+
// Split into to values with each having a reduced number of significant digits
|
|
577
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
578
|
+
vec2 splitValue = vec2(float(value[0]) * lowDivisor, float(value[1]));
|
|
579
|
+
|
|
580
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
581
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
582
|
+
// some equivalent form that does premature rounding.
|
|
583
|
+
float inf = 1.0 / uZero;
|
|
584
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
585
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
586
|
+
|
|
587
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
in highp uint attr_uniqueId;
|
|
592
|
+
in highp float attr_x;
|
|
593
|
+
in highp float attr_y;
|
|
594
|
+
|
|
595
|
+
bool checkSelection_brush(bool empty) {
|
|
596
|
+
return (uParam_brush_x[0] <= attr_x && attr_x <= uParam_brush_x[1]) || (empty && (uParam_brush_x[0] > uParam_brush_x[1]));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
uint accessor_uniqueId_0() {
|
|
601
|
+
return attr_uniqueId;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
uint getScaled_uniqueId() {
|
|
605
|
+
return accessor_uniqueId_0();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#define uniqueId_DEFINED
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
float accessor_x_0() {
|
|
612
|
+
return attr_x;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
//////////////////////////////////////////////////////////////////////
|
|
617
|
+
// Channel: x
|
|
618
|
+
|
|
619
|
+
const vec2 range_x = vec2(0.0, 1.0);
|
|
620
|
+
|
|
621
|
+
float scale_x(float value) {
|
|
622
|
+
int slot = 0;
|
|
623
|
+
vec2 domain = vec2(uDomain_x[slot], uDomain_x[slot + 1]);
|
|
624
|
+
float transformed = scaleLinear(value, domain, range_x);
|
|
625
|
+
return transformed;
|
|
626
|
+
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
float getScaled_x() {
|
|
630
|
+
return scale_x(accessor_x_0());
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#define x_DEFINED
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
float accessor_y_0() {
|
|
637
|
+
return attr_y;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
//////////////////////////////////////////////////////////////////////
|
|
642
|
+
// Channel: y
|
|
643
|
+
|
|
644
|
+
const vec2 range_y = vec2(0.0, 1.0);
|
|
645
|
+
|
|
646
|
+
float scale_y(float value) {
|
|
647
|
+
int slot = 0;
|
|
648
|
+
vec2 domain = vec2(uDomain_y[slot], uDomain_y[slot + 1]);
|
|
649
|
+
float transformed = scaleLinear(value, domain, range_y);
|
|
650
|
+
return transformed;
|
|
651
|
+
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
float getScaled_y() {
|
|
655
|
+
return scale_y(accessor_y_0());
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
#define y_DEFINED
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
float accessor_size_0() {
|
|
662
|
+
// Constant value
|
|
663
|
+
return float(100.0);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
float getScaled_size() {
|
|
667
|
+
return accessor_size_0();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#define size_DEFINED
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
float accessor_semanticScore_0() {
|
|
674
|
+
// Constant value
|
|
675
|
+
return float(0.0);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
float getScaled_semanticScore() {
|
|
679
|
+
return accessor_semanticScore_0();
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#define semanticScore_DEFINED
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
float accessor_shape_0() {
|
|
686
|
+
// Constant value
|
|
687
|
+
return float(0.0);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
float getScaled_shape() {
|
|
691
|
+
return accessor_shape_0();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#define shape_DEFINED
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
float accessor_strokeWidth_0() {
|
|
698
|
+
// Constant value
|
|
699
|
+
return float(0.0);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
float getScaled_strokeWidth() {
|
|
703
|
+
return accessor_strokeWidth_0();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
#define strokeWidth_DEFINED
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
float accessor_dx_0() {
|
|
710
|
+
// Constant value
|
|
711
|
+
return float(0.0);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
float getScaled_dx() {
|
|
715
|
+
return accessor_dx_0();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#define dx_DEFINED
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
float accessor_dy_0() {
|
|
722
|
+
// Constant value
|
|
723
|
+
return float(0.0);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
float getScaled_dy() {
|
|
727
|
+
return accessor_dy_0();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
#define dy_DEFINED
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
float accessor_angle_0() {
|
|
734
|
+
// Constant value
|
|
735
|
+
return float(0.0);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
float getScaled_angle() {
|
|
739
|
+
return accessor_angle_0();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
#define angle_DEFINED
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
vec3 accessor_stroke_0() {
|
|
746
|
+
// Constant value
|
|
747
|
+
return vec3(0.0, 0.0, 0.0);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
vec3 getScaled_stroke() {
|
|
751
|
+
return accessor_stroke_0();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
#define stroke_DEFINED
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
float accessor_strokeOpacity_0() {
|
|
758
|
+
// Constant value
|
|
759
|
+
return float(1.0);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
float getScaled_strokeOpacity() {
|
|
763
|
+
return accessor_strokeOpacity_0();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
#define strokeOpacity_DEFINED
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
vec3 accessor_fill_0() {
|
|
770
|
+
// Constant value
|
|
771
|
+
return vec3(0.2, 0.5333333333333333, 0.8);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
vec3 accessor_fill_1() {
|
|
776
|
+
// Constant value
|
|
777
|
+
return vec3(0.8666666666666667, 0.8666666666666667, 0.8666666666666667);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
vec3 getScaled_fill() {
|
|
781
|
+
if (checkSelection_brush(true)) {
|
|
782
|
+
return accessor_fill_0();
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
return accessor_fill_1();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
#define fill_DEFINED
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
float accessor_fillOpacity_0() {
|
|
793
|
+
// Constant value
|
|
794
|
+
return float(1.0);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
float getScaled_fillOpacity() {
|
|
798
|
+
return accessor_fillOpacity_0();
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
#define fillOpacity_DEFINED
|
|
802
|
+
|
|
803
|
+
bool isPointSelected() {
|
|
804
|
+
return checkSelection_brush(false);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Describes where a sample facet should be shown. Interpolating between the
|
|
810
|
+
* current and target positions/heights allows for transitioning between facet
|
|
811
|
+
* configurations.
|
|
812
|
+
*/
|
|
813
|
+
struct SampleFacetPosition {
|
|
814
|
+
float pos;
|
|
815
|
+
float height;
|
|
816
|
+
float targetPos;
|
|
817
|
+
float targetHeight;
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Trasition fraction [0, 1] between the current and target configurations.
|
|
822
|
+
*/
|
|
823
|
+
uniform float uTransitionOffset;
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
// ----------------------------------------------------------------------------
|
|
827
|
+
|
|
828
|
+
#if !defined(SAMPLE_FACET_UNIFORM) && !defined(SAMPLE_FACET_TEXTURE)
|
|
829
|
+
|
|
830
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
831
|
+
return SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
#elif defined(SAMPLE_FACET_UNIFORM)
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Location and height of the band on the Y axis on a normalized [0, 1] scale.
|
|
838
|
+
* Elements: curr pos, curr height, target pos, target height
|
|
839
|
+
*/
|
|
840
|
+
uniform vec4 uSampleFacet;
|
|
841
|
+
|
|
842
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
843
|
+
return SampleFacetPosition(
|
|
844
|
+
1.0 - uSampleFacet.x - uSampleFacet.y,
|
|
845
|
+
uSampleFacet.y,
|
|
846
|
+
1.0 - uSampleFacet.z - uSampleFacet.w,
|
|
847
|
+
uSampleFacet.w
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
#elif defined(SAMPLE_FACET_TEXTURE)
|
|
852
|
+
|
|
853
|
+
uniform sampler2D uSampleFacetTexture;
|
|
854
|
+
|
|
855
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
856
|
+
vec4 texel = texelFetch(uSampleFacetTexture, ivec2(int(attr_facetIndex), 0), 0);
|
|
857
|
+
return SampleFacetPosition(
|
|
858
|
+
1.0 - texel.r - texel.g,
|
|
859
|
+
texel.g,
|
|
860
|
+
1.0 - texel.r - texel.g,
|
|
861
|
+
texel.g);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
#endif
|
|
865
|
+
|
|
866
|
+
// ----------------------------------------------------------------------------
|
|
867
|
+
|
|
868
|
+
bool isFacetedSamples(SampleFacetPosition facetPos) {
|
|
869
|
+
return facetPos != SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
bool isFacetedSamples() {
|
|
873
|
+
return isFacetedSamples(getSampleFacetPos());
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
bool isInTransit() {
|
|
877
|
+
return uTransitionOffset > 0.0;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
float getTransitionFraction(float xPos) {
|
|
881
|
+
return smoothstep(0.0, 0.7 + uTransitionOffset, (xPos - uTransitionOffset) * 2.0);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
vec2 applySampleFacet(vec2 pos) {
|
|
885
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
886
|
+
|
|
887
|
+
if (!isFacetedSamples(facetPos)) {
|
|
888
|
+
return pos;
|
|
889
|
+
} else if (isInTransit()) {
|
|
890
|
+
vec2 interpolated = mix(
|
|
891
|
+
vec2(facetPos.pos, facetPos.height),
|
|
892
|
+
vec2(facetPos.targetPos, facetPos.targetHeight),
|
|
893
|
+
getTransitionFraction(pos.x));
|
|
894
|
+
return vec2(pos.x, interpolated[0] + pos.y * interpolated[1]);
|
|
895
|
+
} else {
|
|
896
|
+
return vec2(pos.x, facetPos.pos + pos.y * facetPos.height);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
float getSampleFacetHeight(vec2 pos) {
|
|
901
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
902
|
+
|
|
903
|
+
if (!isFacetedSamples(facetPos)) {
|
|
904
|
+
return 1.0;
|
|
905
|
+
} else if (isInTransit()) {
|
|
906
|
+
return mix(
|
|
907
|
+
facetPos.height,
|
|
908
|
+
facetPos.targetHeight,
|
|
909
|
+
getTransitionFraction(pos.x));
|
|
910
|
+
} else {
|
|
911
|
+
return facetPos.height;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
/*
|
|
917
|
+
* Based on concepts presented at:
|
|
918
|
+
* https://webglfundamentals.org/webgl/lessons/webgl-picking.html
|
|
919
|
+
* https://deck.gl/docs/developer-guide/custom-layers/picking
|
|
920
|
+
*/
|
|
921
|
+
|
|
922
|
+
out highp vec4 vPickingColor;
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Passes the unique id to the fragment shader as a color if picking is enabled.
|
|
926
|
+
* Returns true if picking is enabled.
|
|
927
|
+
*/
|
|
928
|
+
bool setupPicking() {
|
|
929
|
+
if (uPickingEnabled) {
|
|
930
|
+
#ifdef uniqueId_DEFINED
|
|
931
|
+
uint id = attr_uniqueId;
|
|
932
|
+
vPickingColor = vec4(
|
|
933
|
+
ivec4(id >> 0, id >> 8, id >> 16, id >> 24) & 0xFF
|
|
934
|
+
) / float(0xFF);
|
|
935
|
+
#else
|
|
936
|
+
vPickingColor = vec4(1.0);
|
|
937
|
+
#endif
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
flat out float vRadius;
|
|
945
|
+
flat out float vRadiusWithPadding;
|
|
946
|
+
flat out lowp vec4 vFillColor;
|
|
947
|
+
flat out lowp vec4 vStrokeColor;
|
|
948
|
+
flat out lowp float vShape;
|
|
949
|
+
flat out lowp float vHalfStrokeWidth;
|
|
950
|
+
flat out mat2 vRotationMatrix;
|
|
951
|
+
|
|
952
|
+
// Copypaste from fragment shader
|
|
953
|
+
const float CIRCLE = 0.0;
|
|
954
|
+
const float SQUARE = 1.0;
|
|
955
|
+
const float CROSS = 2.0;
|
|
956
|
+
const float DIAMOND = 3.0;
|
|
957
|
+
const float TRIANGLE_UP = 4.0;
|
|
958
|
+
const float TRIANGLE_RIGHT = 5.0;
|
|
959
|
+
const float TRIANGLE_DOWN = 6.0;
|
|
960
|
+
const float TRIANGLE_LEFT = 7.0;
|
|
961
|
+
const float TICK_UP = 8.0;
|
|
962
|
+
const float TICK_RIGHT = 9.0;
|
|
963
|
+
const float TICK_DOWN = 10.0;
|
|
964
|
+
const float TICK_LEFT = 11.0;
|
|
965
|
+
|
|
966
|
+
float computeSemanticThresholdFactor() {
|
|
967
|
+
// TODO: add smooth transition
|
|
968
|
+
return getScaled_semanticScore() >= uSemanticThreshold ? 1.0 : 0.0;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// TODO: Move this into common.glsl or something
|
|
972
|
+
vec2 getDxDy() {
|
|
973
|
+
#if defined(dx_DEFINED) || defined(dy_DEFINED)
|
|
974
|
+
return vec2(getScaled_dx(), getScaled_dy()) / uViewportSize;
|
|
975
|
+
#else
|
|
976
|
+
return vec2(0.0, 0.0);
|
|
977
|
+
#endif
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
void main(void) {
|
|
981
|
+
float shapeAngle = 0.0;
|
|
982
|
+
|
|
983
|
+
// Selected points should always be visible
|
|
984
|
+
float semanticThresholdFactor = isPointSelected()
|
|
985
|
+
? 1.0
|
|
986
|
+
: computeSemanticThresholdFactor();
|
|
987
|
+
|
|
988
|
+
if (semanticThresholdFactor <= 0.0) {
|
|
989
|
+
gl_PointSize = 0.0;
|
|
990
|
+
// Place the vertex outside the viewport. The default (0, 0) makes this super-slow
|
|
991
|
+
// on Apple Silicon. Probably related to the tile-based GPU architecture.
|
|
992
|
+
gl_Position = vec4(100.0, 0.0, 0.0, 0.0);
|
|
993
|
+
// Exit early. MAY prevent some unnecessary calculations.
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
float size = getScaled_size();
|
|
998
|
+
vec2 pos = vec2(getScaled_x(), getScaled_y()) + getDxDy();
|
|
999
|
+
|
|
1000
|
+
gl_Position = unitToNdc(applySampleFacet(pos));
|
|
1001
|
+
|
|
1002
|
+
float strokeWidth = getScaled_strokeWidth();
|
|
1003
|
+
|
|
1004
|
+
float diameter = sqrt(size) *
|
|
1005
|
+
uScaleFactor *
|
|
1006
|
+
semanticThresholdFactor;
|
|
1007
|
+
|
|
1008
|
+
// Clamp minimum size and adjust opacity instead. Yields more pleasing result,
|
|
1009
|
+
// no flickering etc.
|
|
1010
|
+
float opacity = uViewOpacity;
|
|
1011
|
+
if (strokeWidth <= 0.0 || uInwardStroke) {
|
|
1012
|
+
float minDiameter = 1.0 / uDevicePixelRatio;
|
|
1013
|
+
if (diameter < minDiameter) {
|
|
1014
|
+
// We do some "cheap" gamma correction here. It breaks on dark background, though.
|
|
1015
|
+
// First we take a square of the size and then apply "gamma" of 1.5.
|
|
1016
|
+
opacity *= pow(diameter / minDiameter, 2.5);
|
|
1017
|
+
diameter = minDiameter;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
float fillOpa = getScaled_fillOpacity() * opacity;
|
|
1022
|
+
float strokeOpa = getScaled_strokeOpacity() * opacity;
|
|
1023
|
+
|
|
1024
|
+
vShape = getScaled_shape();
|
|
1025
|
+
|
|
1026
|
+
// Circle doesn't have sharp corners. Do some special optimizations to minimize the point size.
|
|
1027
|
+
bool circle = vShape == 0.0;
|
|
1028
|
+
|
|
1029
|
+
if (vShape > TICK_UP && vShape <= TICK_LEFT) {
|
|
1030
|
+
shapeAngle = (vShape - TICK_UP) * 90.0;
|
|
1031
|
+
vShape = TICK_UP;
|
|
1032
|
+
} else if (vShape > TRIANGLE_UP && vShape <= TRIANGLE_LEFT) {
|
|
1033
|
+
shapeAngle = (vShape - TRIANGLE_UP) * 90.0;
|
|
1034
|
+
vShape = TRIANGLE_UP;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
float angleInDegrees = getScaled_angle();
|
|
1038
|
+
float angle = -(shapeAngle + angleInDegrees) * PI / 180.0;
|
|
1039
|
+
float sinTheta = sin(angle);
|
|
1040
|
+
float cosTheta = cos(angle);
|
|
1041
|
+
vRotationMatrix = mat2(cosTheta, sinTheta, -sinTheta, cosTheta);
|
|
1042
|
+
|
|
1043
|
+
// Not needed if we would draw rotated quads instead of gl.POINTS
|
|
1044
|
+
float roomForRotation = circle ? 1.0 : sin(mod(angle, PI / 2.0) + PI / 4.0) / sin(PI / 4.0);
|
|
1045
|
+
|
|
1046
|
+
float aaPadding = 1.0 / uDevicePixelRatio;
|
|
1047
|
+
float rotationPadding = (diameter * roomForRotation) - diameter;
|
|
1048
|
+
// sqrt(3.0) ensures that the angles of equilateral triangles have enough room
|
|
1049
|
+
float strokePadding = uInwardStroke ? 0.0 : strokeWidth * (circle ? 1.0 : sqrt(3.0));
|
|
1050
|
+
float padding = rotationPadding + strokePadding + aaPadding;
|
|
1051
|
+
|
|
1052
|
+
gl_PointSize = max(
|
|
1053
|
+
(diameter + padding),
|
|
1054
|
+
uPickingEnabled ? uMinPickingSize : 0.0
|
|
1055
|
+
) * uDevicePixelRatio;
|
|
1056
|
+
|
|
1057
|
+
vRadius = diameter / 2.0;
|
|
1058
|
+
vRadiusWithPadding = vRadius + padding / 2.0;
|
|
1059
|
+
|
|
1060
|
+
vHalfStrokeWidth = strokeWidth / 2.0;
|
|
1061
|
+
|
|
1062
|
+
vFillColor = vec4(getScaled_fill() * fillOpa, fillOpa);
|
|
1063
|
+
vStrokeColor = vec4(getScaled_stroke() * strokeOpa, strokeOpa);
|
|
1064
|
+
|
|
1065
|
+
setupPicking();
|
|
1066
|
+
}
|
|
1067
|
+
",
|
|
1068
|
+
}
|
|
1069
|
+
`;
|
|
1070
|
+
|
|
1071
|
+
exports[`generated shader snapshots > penguins scatter plot example 1`] = `
|
|
1072
|
+
{
|
|
1073
|
+
"fragment": "precision highp float;
|
|
1074
|
+
precision highp int;
|
|
1075
|
+
|
|
1076
|
+
// view: viewRoot/scatterPlot
|
|
1077
|
+
|
|
1078
|
+
layout(std140) uniform Mark {
|
|
1079
|
+
/**
|
|
1080
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
1081
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
1082
|
+
* geometric zoom, etc.
|
|
1083
|
+
*/
|
|
1084
|
+
uniform bool uInwardStroke;
|
|
1085
|
+
|
|
1086
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
1087
|
+
uniform float uMinPickingSize;
|
|
1088
|
+
|
|
1089
|
+
/** Scale factor for geometric zoom */
|
|
1090
|
+
uniform mediump float uScaleFactor;
|
|
1091
|
+
|
|
1092
|
+
uniform mediump float uZoomLevel;
|
|
1093
|
+
uniform highp float uSemanticThreshold;
|
|
1094
|
+
|
|
1095
|
+
uniform mediump float uGradientStrength;
|
|
1096
|
+
|
|
1097
|
+
// Selection parameter
|
|
1098
|
+
uniform highp float[2] uParam_brush_x;
|
|
1099
|
+
// Selection parameter
|
|
1100
|
+
uniform highp float[2] uParam_brush_y;
|
|
1101
|
+
mediump float uDomain_x[2];
|
|
1102
|
+
|
|
1103
|
+
mediump float uDomain_y[2];
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
#define PI 3.141593
|
|
1113
|
+
|
|
1114
|
+
uniform View {
|
|
1115
|
+
/** Offset in "unit" units */
|
|
1116
|
+
mediump vec2 uViewOffset;
|
|
1117
|
+
mediump vec2 uViewScale;
|
|
1118
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
1119
|
+
mediump vec2 uViewportSize;
|
|
1120
|
+
lowp float uDevicePixelRatio;
|
|
1121
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
1122
|
+
// that is rendered with the specified opacity.
|
|
1123
|
+
lowp float uViewOpacity;
|
|
1124
|
+
bool uPickingEnabled;
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
1130
|
+
* (0, 0) is at the bottom left corner.
|
|
1131
|
+
*/
|
|
1132
|
+
vec4 unitToNdc(vec2 coord) {
|
|
1133
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
vec4 unitToNdc(float x, float y) {
|
|
1137
|
+
return unitToNdc(vec2(x, y));
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
1141
|
+
return unitToNdc(coord / uViewportSize);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
1145
|
+
return pixelsToNdc(vec2(x, y));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
1149
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
1153
|
+
|
|
1154
|
+
highp uint hash32(highp uint key) {
|
|
1155
|
+
highp uint v = key;
|
|
1156
|
+
v ^= v >> 16u;
|
|
1157
|
+
v *= 0x7feb352du;
|
|
1158
|
+
v ^= v >> 15u;
|
|
1159
|
+
v *= 0x846ca68bu;
|
|
1160
|
+
v ^= v >> 16u;
|
|
1161
|
+
return v;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
1165
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
1166
|
+
ivec2 texSize = textureSize(s, 0);
|
|
1167
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
1171
|
+
ivec2 texSize = textureSize(s, 0);
|
|
1172
|
+
highp uint width = uint(texSize.x);
|
|
1173
|
+
highp uint size = width * uint(texSize.y);
|
|
1174
|
+
highp uint mask = size - 1u;
|
|
1175
|
+
highp uint index = hash32(value) & mask;
|
|
1176
|
+
|
|
1177
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
1178
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
1179
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
1180
|
+
if (entry == value) {
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
index = (index + 1u) & mask;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
1194
|
+
*/
|
|
1195
|
+
float getGammaForColor(vec3 rgb) {
|
|
1196
|
+
return mix(
|
|
1197
|
+
1.25,
|
|
1198
|
+
0.75,
|
|
1199
|
+
// RGB should be linearized but this is good enough for now
|
|
1200
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
1204
|
+
|
|
1205
|
+
// TODO: include the following only in fragment shaders
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Specialized linearstep for doing antialiasing
|
|
1209
|
+
*/
|
|
1210
|
+
float distanceToRatio(float d) {
|
|
1211
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
1215
|
+
if (halfStrokeWidth > 0.0) {
|
|
1216
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
1217
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
1218
|
+
return mix(
|
|
1219
|
+
stroke,
|
|
1220
|
+
d <= 0.0 ? fill : background,
|
|
1221
|
+
distanceToRatio(sd));
|
|
1222
|
+
} else {
|
|
1223
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
in highp vec4 vPickingColor;
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
const lowp vec4 white = vec4(1.0);
|
|
1232
|
+
const lowp vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
|
|
1233
|
+
|
|
1234
|
+
flat in float vRadius;
|
|
1235
|
+
flat in float vRadiusWithPadding;
|
|
1236
|
+
|
|
1237
|
+
flat in lowp vec4 vFillColor;
|
|
1238
|
+
flat in lowp vec4 vStrokeColor;
|
|
1239
|
+
flat in lowp float vShape;
|
|
1240
|
+
flat in lowp float vHalfStrokeWidth;
|
|
1241
|
+
|
|
1242
|
+
flat in mat2 vRotationMatrix;
|
|
1243
|
+
|
|
1244
|
+
out lowp vec4 fragColor;
|
|
1245
|
+
|
|
1246
|
+
// Copypaste from vertex shader
|
|
1247
|
+
const float CIRCLE = 0.0;
|
|
1248
|
+
const float SQUARE = 1.0;
|
|
1249
|
+
const float CROSS = 2.0;
|
|
1250
|
+
const float DIAMOND = 3.0;
|
|
1251
|
+
const float TRIANGLE_UP = 4.0;
|
|
1252
|
+
const float TICK_UP = 8.0;
|
|
1253
|
+
|
|
1254
|
+
// The distance functions are inspired by:
|
|
1255
|
+
// http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
|
1256
|
+
// However, these are not true distance functions, because the corners need to be sharp.
|
|
1257
|
+
|
|
1258
|
+
float circle(vec2 p, float r) {
|
|
1259
|
+
return length(p) - r;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
float square(vec2 p, float r) {
|
|
1263
|
+
p = abs(p);
|
|
1264
|
+
return max(p.x, p.y) - r;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
float tickUp(vec2 p, float r) {
|
|
1268
|
+
float halfR = r * 0.5;
|
|
1269
|
+
p.y += halfR;
|
|
1270
|
+
p = abs(p);
|
|
1271
|
+
return max(p.x - r * 0.15, p.y - halfR);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
float equilateralTriangle(vec2 p, float r) {
|
|
1275
|
+
p.y = -p.y;
|
|
1276
|
+
float k = sqrt(3.0);
|
|
1277
|
+
float kr = k * r;
|
|
1278
|
+
p.y -= kr / 2.0;
|
|
1279
|
+
return max((abs(p.x) * k + p.y) / 2.0, -p.y - kr);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
float crossShape(vec2 p, float r) {
|
|
1283
|
+
p = abs(p);
|
|
1284
|
+
|
|
1285
|
+
vec2 b = vec2(0.4, 1.0) * r;
|
|
1286
|
+
vec2 v = abs(p) - b.xy;
|
|
1287
|
+
vec2 h = abs(p) - b.yx;
|
|
1288
|
+
return min(max(v.x, v.y), max(h.x, h.y));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
float diamond(vec2 p, float r) {
|
|
1292
|
+
p = abs(p);
|
|
1293
|
+
return (max(abs(p.x - p.y), abs(p.x + p.y)) - r) / sqrt(2.0);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
void main() {
|
|
1297
|
+
float d;
|
|
1298
|
+
|
|
1299
|
+
/** Normalized point coord */
|
|
1300
|
+
vec2 p = vRotationMatrix * (2.0 * gl_PointCoord - 1.0) * vRadiusWithPadding;
|
|
1301
|
+
float r = vRadius;
|
|
1302
|
+
|
|
1303
|
+
// We could also use textures here. Could even be faster, because we have plenty of branching here.
|
|
1304
|
+
if (vShape == CIRCLE) {
|
|
1305
|
+
d = circle(p, r);
|
|
1306
|
+
|
|
1307
|
+
} else if (vShape == SQUARE) {
|
|
1308
|
+
d = square(p, r);
|
|
1309
|
+
|
|
1310
|
+
} else if (vShape == CROSS) {
|
|
1311
|
+
d = crossShape(p, r);
|
|
1312
|
+
|
|
1313
|
+
} else if (vShape == DIAMOND) {
|
|
1314
|
+
d = diamond(p, r);
|
|
1315
|
+
|
|
1316
|
+
} else if (vShape == TRIANGLE_UP) {
|
|
1317
|
+
d = equilateralTriangle(p, r);
|
|
1318
|
+
|
|
1319
|
+
} else if (vShape == TICK_UP) {
|
|
1320
|
+
d = tickUp(p, r);
|
|
1321
|
+
|
|
1322
|
+
} else {
|
|
1323
|
+
d = 0.0;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (!uPickingEnabled) {
|
|
1327
|
+
lowp vec4 fillColor = mix(vFillColor, white, -d * uGradientStrength / vRadius);
|
|
1328
|
+
|
|
1329
|
+
fragColor = distanceToColor(
|
|
1330
|
+
d + (uInwardStroke ? vHalfStrokeWidth : 0.0),
|
|
1331
|
+
fillColor,
|
|
1332
|
+
vStrokeColor,
|
|
1333
|
+
vec4(0.0),
|
|
1334
|
+
vHalfStrokeWidth);
|
|
1335
|
+
|
|
1336
|
+
} else if (d - vHalfStrokeWidth <= 0.0) {
|
|
1337
|
+
fragColor = vPickingColor;
|
|
1338
|
+
|
|
1339
|
+
} else {
|
|
1340
|
+
discard;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
",
|
|
1345
|
+
"vertex": "precision highp float;
|
|
1346
|
+
precision highp int;
|
|
1347
|
+
|
|
1348
|
+
// view: viewRoot/scatterPlot
|
|
1349
|
+
|
|
1350
|
+
layout(std140) uniform Mark {
|
|
1351
|
+
/**
|
|
1352
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
1353
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
1354
|
+
* geometric zoom, etc.
|
|
1355
|
+
*/
|
|
1356
|
+
uniform bool uInwardStroke;
|
|
1357
|
+
|
|
1358
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
1359
|
+
uniform float uMinPickingSize;
|
|
1360
|
+
|
|
1361
|
+
/** Scale factor for geometric zoom */
|
|
1362
|
+
uniform mediump float uScaleFactor;
|
|
1363
|
+
|
|
1364
|
+
uniform mediump float uZoomLevel;
|
|
1365
|
+
uniform highp float uSemanticThreshold;
|
|
1366
|
+
|
|
1367
|
+
uniform mediump float uGradientStrength;
|
|
1368
|
+
|
|
1369
|
+
// Selection parameter
|
|
1370
|
+
uniform highp float[2] uParam_brush_x;
|
|
1371
|
+
// Selection parameter
|
|
1372
|
+
uniform highp float[2] uParam_brush_y;
|
|
1373
|
+
mediump float uDomain_x[2];
|
|
1374
|
+
|
|
1375
|
+
mediump float uDomain_y[2];
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
#define PI 3.141593
|
|
1385
|
+
|
|
1386
|
+
uniform View {
|
|
1387
|
+
/** Offset in "unit" units */
|
|
1388
|
+
mediump vec2 uViewOffset;
|
|
1389
|
+
mediump vec2 uViewScale;
|
|
1390
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
1391
|
+
mediump vec2 uViewportSize;
|
|
1392
|
+
lowp float uDevicePixelRatio;
|
|
1393
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
1394
|
+
// that is rendered with the specified opacity.
|
|
1395
|
+
lowp float uViewOpacity;
|
|
1396
|
+
bool uPickingEnabled;
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
1402
|
+
* (0, 0) is at the bottom left corner.
|
|
1403
|
+
*/
|
|
1404
|
+
vec4 unitToNdc(vec2 coord) {
|
|
1405
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
vec4 unitToNdc(float x, float y) {
|
|
1409
|
+
return unitToNdc(vec2(x, y));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
1413
|
+
return unitToNdc(coord / uViewportSize);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
1417
|
+
return pixelsToNdc(vec2(x, y));
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
1421
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
1425
|
+
|
|
1426
|
+
highp uint hash32(highp uint key) {
|
|
1427
|
+
highp uint v = key;
|
|
1428
|
+
v ^= v >> 16u;
|
|
1429
|
+
v *= 0x7feb352du;
|
|
1430
|
+
v ^= v >> 15u;
|
|
1431
|
+
v *= 0x846ca68bu;
|
|
1432
|
+
v ^= v >> 16u;
|
|
1433
|
+
return v;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
1437
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
1438
|
+
ivec2 texSize = textureSize(s, 0);
|
|
1439
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
1443
|
+
ivec2 texSize = textureSize(s, 0);
|
|
1444
|
+
highp uint width = uint(texSize.x);
|
|
1445
|
+
highp uint size = width * uint(texSize.y);
|
|
1446
|
+
highp uint mask = size - 1u;
|
|
1447
|
+
highp uint index = hash32(value) & mask;
|
|
1448
|
+
|
|
1449
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
1450
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
1451
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
1452
|
+
if (entry == value) {
|
|
1453
|
+
return true;
|
|
1454
|
+
}
|
|
1455
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
index = (index + 1u) & mask;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
1466
|
+
*/
|
|
1467
|
+
float getGammaForColor(vec3 rgb) {
|
|
1468
|
+
return mix(
|
|
1469
|
+
1.25,
|
|
1470
|
+
0.75,
|
|
1471
|
+
// RGB should be linearized but this is good enough for now
|
|
1472
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
1476
|
+
|
|
1477
|
+
// TODO: include the following only in fragment shaders
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Specialized linearstep for doing antialiasing
|
|
1481
|
+
*/
|
|
1482
|
+
float distanceToRatio(float d) {
|
|
1483
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
1487
|
+
if (halfStrokeWidth > 0.0) {
|
|
1488
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
1489
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
1490
|
+
return mix(
|
|
1491
|
+
stroke,
|
|
1492
|
+
d <= 0.0 ? fill : background,
|
|
1493
|
+
distanceToRatio(sd));
|
|
1494
|
+
} else {
|
|
1495
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
uniform highp float uZero;
|
|
1501
|
+
|
|
1502
|
+
// Utils ------------
|
|
1503
|
+
|
|
1504
|
+
vec3 getDiscreteColor(sampler2D s, int index) {
|
|
1505
|
+
return texelFetch(s, ivec2(index % textureSize(s, 0).x, 0), 0).rgb;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
vec3 getInterpolatedColor(sampler2D s, float unitValue) {
|
|
1509
|
+
return texture(s, vec2(unitValue, 0.0)).rgb;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
float clampToRange(float value, vec2 range) {
|
|
1513
|
+
return clamp(value, min(range[0], range[1]), max(range[0], range[1]));
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Scales ------------
|
|
1517
|
+
// Based on d3 scales: https://github.com/d3/d3-scale
|
|
1518
|
+
|
|
1519
|
+
float scaleIdentity(float value) {
|
|
1520
|
+
return value;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
float scaleIdentity(uint value) {
|
|
1524
|
+
return float(value);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
float scaleLinear(float value, vec2 domain, vec2 range) {
|
|
1528
|
+
float domainSpan = domain[1] - domain[0];
|
|
1529
|
+
float rangeSpan = range[1] - range[0];
|
|
1530
|
+
return (value - domain[0]) / domainSpan * rangeSpan + range[0];
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
float scaleLog(float value, vec2 domain, vec2 range, float base) {
|
|
1534
|
+
// y = m log(x) + b
|
|
1535
|
+
// TODO: Perf optimization: precalculate log domain in js.
|
|
1536
|
+
// TODO: Reversed domain, etc
|
|
1537
|
+
return scaleLinear(log(value) / log(base), log(domain) / log(base), range);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
float symlog(float value, float constant) {
|
|
1541
|
+
// WARNING: emulating log1p with log(x + 1). Small numbers are likely to
|
|
1542
|
+
// have significant precision problems.
|
|
1543
|
+
return sign(value) * log(abs(value / constant) + 1.0);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
float scaleSymlog(float value, vec2 domain, vec2 range, float constant) {
|
|
1547
|
+
return scaleLinear(
|
|
1548
|
+
symlog(value, constant),
|
|
1549
|
+
vec2(symlog(domain[0], constant), symlog(domain[1], constant)),
|
|
1550
|
+
range
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
float scalePow(float value, vec2 domain, vec2 range, float exponent) {
|
|
1555
|
+
// y = mx^k + b
|
|
1556
|
+
// TODO: Perf optimization: precalculate pow domain in js.
|
|
1557
|
+
// TODO: Reversed domain, etc
|
|
1558
|
+
return scaleLinear(
|
|
1559
|
+
pow(abs(value), exponent) * sign(value),
|
|
1560
|
+
pow(abs(domain), vec2(exponent)) * sign(domain),
|
|
1561
|
+
range
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// TODO: scaleThreshold
|
|
1566
|
+
// TODO: scaleQuantile (special case of threshold scale)
|
|
1567
|
+
|
|
1568
|
+
// TODO: domainExtent should be uint
|
|
1569
|
+
float scaleBand(uint value, vec2 domainExtent, vec2 range,
|
|
1570
|
+
float paddingInner, float paddingOuter,
|
|
1571
|
+
float align, float band) {
|
|
1572
|
+
|
|
1573
|
+
// TODO: reverse
|
|
1574
|
+
float start = range[0];
|
|
1575
|
+
float stop = range[1];
|
|
1576
|
+
float rangeSpan = stop - start;
|
|
1577
|
+
|
|
1578
|
+
float n = domainExtent[1] - domainExtent[0];
|
|
1579
|
+
|
|
1580
|
+
// This fix departs from Vega and d3: https://github.com/vega/vega/issues/3357#issuecomment-1063253596
|
|
1581
|
+
paddingInner = int(n) > 1 ? paddingInner : 0.0;
|
|
1582
|
+
|
|
1583
|
+
// Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
|
|
1584
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
1585
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
1586
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
1587
|
+
|
|
1588
|
+
return start + (float(value) - domainExtent[0]) * step + bandwidth * band;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
const int lowBits = 12;
|
|
1592
|
+
const float lowDivisor = pow(2.0, float(lowBits));
|
|
1593
|
+
const uint lowMask = uint(lowDivisor - 1.0);
|
|
1594
|
+
|
|
1595
|
+
vec2 splitUint(uint value) {
|
|
1596
|
+
uint valueLo = value & lowMask;
|
|
1597
|
+
uint valueHi = value - valueLo;
|
|
1598
|
+
return vec2(float(valueHi), float(valueLo));
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* High precision variant of scaleBand for index/locus scales
|
|
1603
|
+
*/
|
|
1604
|
+
float scaleBandHp(uint value, vec3 domainExtent, vec2 range,
|
|
1605
|
+
float paddingInner, float paddingOuter,
|
|
1606
|
+
float align, float band) {
|
|
1607
|
+
|
|
1608
|
+
// TODO: reverse
|
|
1609
|
+
float start = range[0];
|
|
1610
|
+
float stop = range[1];
|
|
1611
|
+
float rangeSpan = stop - start;
|
|
1612
|
+
|
|
1613
|
+
vec2 domainStart = domainExtent.xy;
|
|
1614
|
+
float n = domainExtent[2];
|
|
1615
|
+
|
|
1616
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
1617
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
1618
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
1619
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
1620
|
+
|
|
1621
|
+
// Split into to values with each having a reduced number of significant digits
|
|
1622
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
1623
|
+
vec2 splitValue = splitUint(value);
|
|
1624
|
+
|
|
1625
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
1626
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
1627
|
+
// some equivalent form that does premature rounding.
|
|
1628
|
+
float inf = 1.0 / uZero;
|
|
1629
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
1630
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
1631
|
+
|
|
1632
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* High precision variant of scaleBand for index/locus scales for large
|
|
1637
|
+
* domains where 32bit uints are not sufficient to represent the domain.
|
|
1638
|
+
*/
|
|
1639
|
+
float scaleBandHp(uvec2 value, vec3 domainExtent, vec2 range,
|
|
1640
|
+
float paddingInner, float paddingOuter,
|
|
1641
|
+
float align, float band) {
|
|
1642
|
+
|
|
1643
|
+
// TODO: reverse
|
|
1644
|
+
float start = range[0];
|
|
1645
|
+
float stop = range[1];
|
|
1646
|
+
float rangeSpan = stop - start;
|
|
1647
|
+
|
|
1648
|
+
vec2 domainStart = domainExtent.xy;
|
|
1649
|
+
float n = domainExtent[2];
|
|
1650
|
+
|
|
1651
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
1652
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
1653
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
1654
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
1655
|
+
|
|
1656
|
+
// Split into to values with each having a reduced number of significant digits
|
|
1657
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
1658
|
+
vec2 splitValue = vec2(float(value[0]) * lowDivisor, float(value[1]));
|
|
1659
|
+
|
|
1660
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
1661
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
1662
|
+
// some equivalent form that does premature rounding.
|
|
1663
|
+
float inf = 1.0 / uZero;
|
|
1664
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
1665
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
1666
|
+
|
|
1667
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
in highp uint attr_uniqueId;
|
|
1672
|
+
in highp float attr_x;
|
|
1673
|
+
in highp float attr_y;
|
|
1674
|
+
in highp uint attr_stroke;
|
|
1675
|
+
in highp uint attr_fill;
|
|
1676
|
+
|
|
1677
|
+
bool checkSelection_brush(bool empty) {
|
|
1678
|
+
return (uParam_brush_x[0] <= attr_x && attr_x <= uParam_brush_x[1]) && (uParam_brush_y[0] <= attr_y && attr_y <= uParam_brush_y[1]) || (empty && (uParam_brush_x[0] > uParam_brush_x[1] || uParam_brush_y[0] > uParam_brush_y[1]));
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
|
|
1682
|
+
uint accessor_uniqueId_0() {
|
|
1683
|
+
return attr_uniqueId;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
uint getScaled_uniqueId() {
|
|
1687
|
+
return accessor_uniqueId_0();
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
#define uniqueId_DEFINED
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
float accessor_x_0() {
|
|
1694
|
+
return attr_x;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
//////////////////////////////////////////////////////////////////////
|
|
1699
|
+
// Channel: x
|
|
1700
|
+
|
|
1701
|
+
const vec2 range_x = vec2(0.0, 1.0);
|
|
1702
|
+
|
|
1703
|
+
float scale_x(float value) {
|
|
1704
|
+
int slot = 0;
|
|
1705
|
+
vec2 domain = vec2(uDomain_x[slot], uDomain_x[slot + 1]);
|
|
1706
|
+
float transformed = scaleLinear(value, domain, range_x);
|
|
1707
|
+
return transformed;
|
|
1708
|
+
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
float getScaled_x() {
|
|
1712
|
+
return scale_x(accessor_x_0());
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
#define x_DEFINED
|
|
1716
|
+
|
|
1717
|
+
|
|
1718
|
+
float accessor_y_0() {
|
|
1719
|
+
return attr_y;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
//////////////////////////////////////////////////////////////////////
|
|
1724
|
+
// Channel: y
|
|
1725
|
+
|
|
1726
|
+
const vec2 range_y = vec2(0.0, 1.0);
|
|
1727
|
+
|
|
1728
|
+
float scale_y(float value) {
|
|
1729
|
+
int slot = 0;
|
|
1730
|
+
vec2 domain = vec2(uDomain_y[slot], uDomain_y[slot + 1]);
|
|
1731
|
+
float transformed = scaleLinear(value, domain, range_y);
|
|
1732
|
+
return transformed;
|
|
1733
|
+
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
float getScaled_y() {
|
|
1737
|
+
return scale_y(accessor_y_0());
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
#define y_DEFINED
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
float accessor_size_0() {
|
|
1744
|
+
// Constant value
|
|
1745
|
+
return float(40.0);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
float getScaled_size() {
|
|
1749
|
+
return accessor_size_0();
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
#define size_DEFINED
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
float accessor_semanticScore_0() {
|
|
1756
|
+
// Constant value
|
|
1757
|
+
return float(0.0);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
float getScaled_semanticScore() {
|
|
1761
|
+
return accessor_semanticScore_0();
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
#define semanticScore_DEFINED
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
float accessor_shape_0() {
|
|
1768
|
+
// Constant value
|
|
1769
|
+
return float(0.0);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
float getScaled_shape() {
|
|
1773
|
+
return accessor_shape_0();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
#define shape_DEFINED
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
float accessor_strokeWidth_0() {
|
|
1780
|
+
// Constant value
|
|
1781
|
+
return float(2.0);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
float getScaled_strokeWidth() {
|
|
1785
|
+
return accessor_strokeWidth_0();
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
#define strokeWidth_DEFINED
|
|
1789
|
+
|
|
1790
|
+
|
|
1791
|
+
float accessor_dx_0() {
|
|
1792
|
+
// Constant value
|
|
1793
|
+
return float(0.0);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
float getScaled_dx() {
|
|
1797
|
+
return accessor_dx_0();
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
#define dx_DEFINED
|
|
1801
|
+
|
|
1802
|
+
|
|
1803
|
+
float accessor_dy_0() {
|
|
1804
|
+
// Constant value
|
|
1805
|
+
return float(0.0);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
float getScaled_dy() {
|
|
1809
|
+
return accessor_dy_0();
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
#define dy_DEFINED
|
|
1813
|
+
|
|
1814
|
+
|
|
1815
|
+
float accessor_angle_0() {
|
|
1816
|
+
// Constant value
|
|
1817
|
+
return float(0.0);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
float getScaled_angle() {
|
|
1821
|
+
return accessor_angle_0();
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
#define angle_DEFINED
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
uint accessor_stroke_0() {
|
|
1828
|
+
return attr_stroke;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
vec3 accessor_stroke_1() {
|
|
1833
|
+
// Constant value
|
|
1834
|
+
return vec3(0.8274509803921568, 0.8274509803921568, 0.8274509803921568);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
|
|
1838
|
+
//////////////////////////////////////////////////////////////////////
|
|
1839
|
+
// Channel: stroke
|
|
1840
|
+
|
|
1841
|
+
uniform sampler2D uRangeTexture_stroke;
|
|
1842
|
+
|
|
1843
|
+
vec3 scale_stroke(uint value) {
|
|
1844
|
+
int slot = 0;
|
|
1845
|
+
float transformed = scaleIdentity(value);
|
|
1846
|
+
return getDiscreteColor(uRangeTexture_stroke, int(transformed));
|
|
1847
|
+
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
vec3 getScaled_stroke() {
|
|
1851
|
+
if (checkSelection_brush(true)) {
|
|
1852
|
+
return scale_stroke(accessor_stroke_0());
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
return accessor_stroke_1();
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
#define stroke_DEFINED
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
float accessor_strokeOpacity_0() {
|
|
1863
|
+
// Constant value
|
|
1864
|
+
return float(0.7);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
float getScaled_strokeOpacity() {
|
|
1868
|
+
return accessor_strokeOpacity_0();
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
#define strokeOpacity_DEFINED
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
uint accessor_fill_0() {
|
|
1875
|
+
return attr_fill;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
vec3 accessor_fill_1() {
|
|
1880
|
+
// Constant value
|
|
1881
|
+
return vec3(0.8274509803921568, 0.8274509803921568, 0.8274509803921568);
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
//////////////////////////////////////////////////////////////////////
|
|
1886
|
+
// Channel: fill
|
|
1887
|
+
|
|
1888
|
+
uniform sampler2D uRangeTexture_fill;
|
|
1889
|
+
|
|
1890
|
+
vec3 scale_fill(uint value) {
|
|
1891
|
+
int slot = 0;
|
|
1892
|
+
float transformed = scaleIdentity(value);
|
|
1893
|
+
return getDiscreteColor(uRangeTexture_fill, int(transformed));
|
|
1894
|
+
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
vec3 getScaled_fill() {
|
|
1898
|
+
if (checkSelection_brush(true)) {
|
|
1899
|
+
return scale_fill(accessor_fill_0());
|
|
1900
|
+
}
|
|
1901
|
+
else {
|
|
1902
|
+
return accessor_fill_1();
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
#define fill_DEFINED
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
float accessor_fillOpacity_0() {
|
|
1910
|
+
// Constant value
|
|
1911
|
+
return float(0.0);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
float getScaled_fillOpacity() {
|
|
1915
|
+
return accessor_fillOpacity_0();
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
#define fillOpacity_DEFINED
|
|
1919
|
+
|
|
1920
|
+
bool isPointSelected() {
|
|
1921
|
+
return checkSelection_brush(false);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* Describes where a sample facet should be shown. Interpolating between the
|
|
1927
|
+
* current and target positions/heights allows for transitioning between facet
|
|
1928
|
+
* configurations.
|
|
1929
|
+
*/
|
|
1930
|
+
struct SampleFacetPosition {
|
|
1931
|
+
float pos;
|
|
1932
|
+
float height;
|
|
1933
|
+
float targetPos;
|
|
1934
|
+
float targetHeight;
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
/**
|
|
1938
|
+
* Trasition fraction [0, 1] between the current and target configurations.
|
|
1939
|
+
*/
|
|
1940
|
+
uniform float uTransitionOffset;
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
// ----------------------------------------------------------------------------
|
|
1944
|
+
|
|
1945
|
+
#if !defined(SAMPLE_FACET_UNIFORM) && !defined(SAMPLE_FACET_TEXTURE)
|
|
1946
|
+
|
|
1947
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
1948
|
+
return SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
#elif defined(SAMPLE_FACET_UNIFORM)
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* Location and height of the band on the Y axis on a normalized [0, 1] scale.
|
|
1955
|
+
* Elements: curr pos, curr height, target pos, target height
|
|
1956
|
+
*/
|
|
1957
|
+
uniform vec4 uSampleFacet;
|
|
1958
|
+
|
|
1959
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
1960
|
+
return SampleFacetPosition(
|
|
1961
|
+
1.0 - uSampleFacet.x - uSampleFacet.y,
|
|
1962
|
+
uSampleFacet.y,
|
|
1963
|
+
1.0 - uSampleFacet.z - uSampleFacet.w,
|
|
1964
|
+
uSampleFacet.w
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
#elif defined(SAMPLE_FACET_TEXTURE)
|
|
1969
|
+
|
|
1970
|
+
uniform sampler2D uSampleFacetTexture;
|
|
1971
|
+
|
|
1972
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
1973
|
+
vec4 texel = texelFetch(uSampleFacetTexture, ivec2(int(attr_facetIndex), 0), 0);
|
|
1974
|
+
return SampleFacetPosition(
|
|
1975
|
+
1.0 - texel.r - texel.g,
|
|
1976
|
+
texel.g,
|
|
1977
|
+
1.0 - texel.r - texel.g,
|
|
1978
|
+
texel.g);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
#endif
|
|
1982
|
+
|
|
1983
|
+
// ----------------------------------------------------------------------------
|
|
1984
|
+
|
|
1985
|
+
bool isFacetedSamples(SampleFacetPosition facetPos) {
|
|
1986
|
+
return facetPos != SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
bool isFacetedSamples() {
|
|
1990
|
+
return isFacetedSamples(getSampleFacetPos());
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
bool isInTransit() {
|
|
1994
|
+
return uTransitionOffset > 0.0;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
float getTransitionFraction(float xPos) {
|
|
1998
|
+
return smoothstep(0.0, 0.7 + uTransitionOffset, (xPos - uTransitionOffset) * 2.0);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
vec2 applySampleFacet(vec2 pos) {
|
|
2002
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
2003
|
+
|
|
2004
|
+
if (!isFacetedSamples(facetPos)) {
|
|
2005
|
+
return pos;
|
|
2006
|
+
} else if (isInTransit()) {
|
|
2007
|
+
vec2 interpolated = mix(
|
|
2008
|
+
vec2(facetPos.pos, facetPos.height),
|
|
2009
|
+
vec2(facetPos.targetPos, facetPos.targetHeight),
|
|
2010
|
+
getTransitionFraction(pos.x));
|
|
2011
|
+
return vec2(pos.x, interpolated[0] + pos.y * interpolated[1]);
|
|
2012
|
+
} else {
|
|
2013
|
+
return vec2(pos.x, facetPos.pos + pos.y * facetPos.height);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
float getSampleFacetHeight(vec2 pos) {
|
|
2018
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
2019
|
+
|
|
2020
|
+
if (!isFacetedSamples(facetPos)) {
|
|
2021
|
+
return 1.0;
|
|
2022
|
+
} else if (isInTransit()) {
|
|
2023
|
+
return mix(
|
|
2024
|
+
facetPos.height,
|
|
2025
|
+
facetPos.targetHeight,
|
|
2026
|
+
getTransitionFraction(pos.x));
|
|
2027
|
+
} else {
|
|
2028
|
+
return facetPos.height;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
|
|
2033
|
+
/*
|
|
2034
|
+
* Based on concepts presented at:
|
|
2035
|
+
* https://webglfundamentals.org/webgl/lessons/webgl-picking.html
|
|
2036
|
+
* https://deck.gl/docs/developer-guide/custom-layers/picking
|
|
2037
|
+
*/
|
|
2038
|
+
|
|
2039
|
+
out highp vec4 vPickingColor;
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* Passes the unique id to the fragment shader as a color if picking is enabled.
|
|
2043
|
+
* Returns true if picking is enabled.
|
|
2044
|
+
*/
|
|
2045
|
+
bool setupPicking() {
|
|
2046
|
+
if (uPickingEnabled) {
|
|
2047
|
+
#ifdef uniqueId_DEFINED
|
|
2048
|
+
uint id = attr_uniqueId;
|
|
2049
|
+
vPickingColor = vec4(
|
|
2050
|
+
ivec4(id >> 0, id >> 8, id >> 16, id >> 24) & 0xFF
|
|
2051
|
+
) / float(0xFF);
|
|
2052
|
+
#else
|
|
2053
|
+
vPickingColor = vec4(1.0);
|
|
2054
|
+
#endif
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
return false;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
|
|
2061
|
+
flat out float vRadius;
|
|
2062
|
+
flat out float vRadiusWithPadding;
|
|
2063
|
+
flat out lowp vec4 vFillColor;
|
|
2064
|
+
flat out lowp vec4 vStrokeColor;
|
|
2065
|
+
flat out lowp float vShape;
|
|
2066
|
+
flat out lowp float vHalfStrokeWidth;
|
|
2067
|
+
flat out mat2 vRotationMatrix;
|
|
2068
|
+
|
|
2069
|
+
// Copypaste from fragment shader
|
|
2070
|
+
const float CIRCLE = 0.0;
|
|
2071
|
+
const float SQUARE = 1.0;
|
|
2072
|
+
const float CROSS = 2.0;
|
|
2073
|
+
const float DIAMOND = 3.0;
|
|
2074
|
+
const float TRIANGLE_UP = 4.0;
|
|
2075
|
+
const float TRIANGLE_RIGHT = 5.0;
|
|
2076
|
+
const float TRIANGLE_DOWN = 6.0;
|
|
2077
|
+
const float TRIANGLE_LEFT = 7.0;
|
|
2078
|
+
const float TICK_UP = 8.0;
|
|
2079
|
+
const float TICK_RIGHT = 9.0;
|
|
2080
|
+
const float TICK_DOWN = 10.0;
|
|
2081
|
+
const float TICK_LEFT = 11.0;
|
|
2082
|
+
|
|
2083
|
+
float computeSemanticThresholdFactor() {
|
|
2084
|
+
// TODO: add smooth transition
|
|
2085
|
+
return getScaled_semanticScore() >= uSemanticThreshold ? 1.0 : 0.0;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// TODO: Move this into common.glsl or something
|
|
2089
|
+
vec2 getDxDy() {
|
|
2090
|
+
#if defined(dx_DEFINED) || defined(dy_DEFINED)
|
|
2091
|
+
return vec2(getScaled_dx(), getScaled_dy()) / uViewportSize;
|
|
2092
|
+
#else
|
|
2093
|
+
return vec2(0.0, 0.0);
|
|
2094
|
+
#endif
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
void main(void) {
|
|
2098
|
+
float shapeAngle = 0.0;
|
|
2099
|
+
|
|
2100
|
+
// Selected points should always be visible
|
|
2101
|
+
float semanticThresholdFactor = isPointSelected()
|
|
2102
|
+
? 1.0
|
|
2103
|
+
: computeSemanticThresholdFactor();
|
|
2104
|
+
|
|
2105
|
+
if (semanticThresholdFactor <= 0.0) {
|
|
2106
|
+
gl_PointSize = 0.0;
|
|
2107
|
+
// Place the vertex outside the viewport. The default (0, 0) makes this super-slow
|
|
2108
|
+
// on Apple Silicon. Probably related to the tile-based GPU architecture.
|
|
2109
|
+
gl_Position = vec4(100.0, 0.0, 0.0, 0.0);
|
|
2110
|
+
// Exit early. MAY prevent some unnecessary calculations.
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
float size = getScaled_size();
|
|
2115
|
+
vec2 pos = vec2(getScaled_x(), getScaled_y()) + getDxDy();
|
|
2116
|
+
|
|
2117
|
+
gl_Position = unitToNdc(applySampleFacet(pos));
|
|
2118
|
+
|
|
2119
|
+
float strokeWidth = getScaled_strokeWidth();
|
|
2120
|
+
|
|
2121
|
+
float diameter = sqrt(size) *
|
|
2122
|
+
uScaleFactor *
|
|
2123
|
+
semanticThresholdFactor;
|
|
2124
|
+
|
|
2125
|
+
// Clamp minimum size and adjust opacity instead. Yields more pleasing result,
|
|
2126
|
+
// no flickering etc.
|
|
2127
|
+
float opacity = uViewOpacity;
|
|
2128
|
+
if (strokeWidth <= 0.0 || uInwardStroke) {
|
|
2129
|
+
float minDiameter = 1.0 / uDevicePixelRatio;
|
|
2130
|
+
if (diameter < minDiameter) {
|
|
2131
|
+
// We do some "cheap" gamma correction here. It breaks on dark background, though.
|
|
2132
|
+
// First we take a square of the size and then apply "gamma" of 1.5.
|
|
2133
|
+
opacity *= pow(diameter / minDiameter, 2.5);
|
|
2134
|
+
diameter = minDiameter;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
float fillOpa = getScaled_fillOpacity() * opacity;
|
|
2139
|
+
float strokeOpa = getScaled_strokeOpacity() * opacity;
|
|
2140
|
+
|
|
2141
|
+
vShape = getScaled_shape();
|
|
2142
|
+
|
|
2143
|
+
// Circle doesn't have sharp corners. Do some special optimizations to minimize the point size.
|
|
2144
|
+
bool circle = vShape == 0.0;
|
|
2145
|
+
|
|
2146
|
+
if (vShape > TICK_UP && vShape <= TICK_LEFT) {
|
|
2147
|
+
shapeAngle = (vShape - TICK_UP) * 90.0;
|
|
2148
|
+
vShape = TICK_UP;
|
|
2149
|
+
} else if (vShape > TRIANGLE_UP && vShape <= TRIANGLE_LEFT) {
|
|
2150
|
+
shapeAngle = (vShape - TRIANGLE_UP) * 90.0;
|
|
2151
|
+
vShape = TRIANGLE_UP;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
float angleInDegrees = getScaled_angle();
|
|
2155
|
+
float angle = -(shapeAngle + angleInDegrees) * PI / 180.0;
|
|
2156
|
+
float sinTheta = sin(angle);
|
|
2157
|
+
float cosTheta = cos(angle);
|
|
2158
|
+
vRotationMatrix = mat2(cosTheta, sinTheta, -sinTheta, cosTheta);
|
|
2159
|
+
|
|
2160
|
+
// Not needed if we would draw rotated quads instead of gl.POINTS
|
|
2161
|
+
float roomForRotation = circle ? 1.0 : sin(mod(angle, PI / 2.0) + PI / 4.0) / sin(PI / 4.0);
|
|
2162
|
+
|
|
2163
|
+
float aaPadding = 1.0 / uDevicePixelRatio;
|
|
2164
|
+
float rotationPadding = (diameter * roomForRotation) - diameter;
|
|
2165
|
+
// sqrt(3.0) ensures that the angles of equilateral triangles have enough room
|
|
2166
|
+
float strokePadding = uInwardStroke ? 0.0 : strokeWidth * (circle ? 1.0 : sqrt(3.0));
|
|
2167
|
+
float padding = rotationPadding + strokePadding + aaPadding;
|
|
2168
|
+
|
|
2169
|
+
gl_PointSize = max(
|
|
2170
|
+
(diameter + padding),
|
|
2171
|
+
uPickingEnabled ? uMinPickingSize : 0.0
|
|
2172
|
+
) * uDevicePixelRatio;
|
|
2173
|
+
|
|
2174
|
+
vRadius = diameter / 2.0;
|
|
2175
|
+
vRadiusWithPadding = vRadius + padding / 2.0;
|
|
2176
|
+
|
|
2177
|
+
vHalfStrokeWidth = strokeWidth / 2.0;
|
|
2178
|
+
|
|
2179
|
+
vFillColor = vec4(getScaled_fill() * fillOpa, fillOpa);
|
|
2180
|
+
vStrokeColor = vec4(getScaled_stroke() * strokeOpa, strokeOpa);
|
|
2181
|
+
|
|
2182
|
+
setupPicking();
|
|
2183
|
+
}
|
|
2184
|
+
",
|
|
2185
|
+
}
|
|
2186
|
+
`;
|
|
2187
|
+
|
|
2188
|
+
exports[`generated shader snapshots > point mark control spec 1`] = `
|
|
2189
|
+
{
|
|
2190
|
+
"fragment": "precision highp float;
|
|
2191
|
+
precision highp int;
|
|
2192
|
+
|
|
2193
|
+
// view: viewRoot
|
|
2194
|
+
|
|
2195
|
+
layout(std140) uniform Mark {
|
|
2196
|
+
/**
|
|
2197
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
2198
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
2199
|
+
* geometric zoom, etc.
|
|
2200
|
+
*/
|
|
2201
|
+
uniform bool uInwardStroke;
|
|
2202
|
+
|
|
2203
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
2204
|
+
uniform float uMinPickingSize;
|
|
2205
|
+
|
|
2206
|
+
/** Scale factor for geometric zoom */
|
|
2207
|
+
uniform mediump float uScaleFactor;
|
|
2208
|
+
|
|
2209
|
+
uniform mediump float uZoomLevel;
|
|
2210
|
+
uniform highp float uSemanticThreshold;
|
|
2211
|
+
|
|
2212
|
+
uniform mediump float uGradientStrength;
|
|
2213
|
+
|
|
2214
|
+
mediump float uDomain_x[2];
|
|
2215
|
+
|
|
2216
|
+
mediump float uDomain_y[2];
|
|
2217
|
+
|
|
2218
|
+
|
|
2219
|
+
|
|
2220
|
+
};
|
|
2221
|
+
|
|
2222
|
+
|
|
2223
|
+
#define PI 3.141593
|
|
2224
|
+
|
|
2225
|
+
uniform View {
|
|
2226
|
+
/** Offset in "unit" units */
|
|
2227
|
+
mediump vec2 uViewOffset;
|
|
2228
|
+
mediump vec2 uViewScale;
|
|
2229
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
2230
|
+
mediump vec2 uViewportSize;
|
|
2231
|
+
lowp float uDevicePixelRatio;
|
|
2232
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
2233
|
+
// that is rendered with the specified opacity.
|
|
2234
|
+
lowp float uViewOpacity;
|
|
2235
|
+
bool uPickingEnabled;
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
|
|
2239
|
+
/**
|
|
2240
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
2241
|
+
* (0, 0) is at the bottom left corner.
|
|
2242
|
+
*/
|
|
2243
|
+
vec4 unitToNdc(vec2 coord) {
|
|
2244
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
vec4 unitToNdc(float x, float y) {
|
|
2248
|
+
return unitToNdc(vec2(x, y));
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
2252
|
+
return unitToNdc(coord / uViewportSize);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
2256
|
+
return pixelsToNdc(vec2(x, y));
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
2260
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
2264
|
+
|
|
2265
|
+
highp uint hash32(highp uint key) {
|
|
2266
|
+
highp uint v = key;
|
|
2267
|
+
v ^= v >> 16u;
|
|
2268
|
+
v *= 0x7feb352du;
|
|
2269
|
+
v ^= v >> 15u;
|
|
2270
|
+
v *= 0x846ca68bu;
|
|
2271
|
+
v ^= v >> 16u;
|
|
2272
|
+
return v;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
2276
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
2277
|
+
ivec2 texSize = textureSize(s, 0);
|
|
2278
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
2282
|
+
ivec2 texSize = textureSize(s, 0);
|
|
2283
|
+
highp uint width = uint(texSize.x);
|
|
2284
|
+
highp uint size = width * uint(texSize.y);
|
|
2285
|
+
highp uint mask = size - 1u;
|
|
2286
|
+
highp uint index = hash32(value) & mask;
|
|
2287
|
+
|
|
2288
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
2289
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
2290
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
2291
|
+
if (entry == value) {
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
2295
|
+
return false;
|
|
2296
|
+
}
|
|
2297
|
+
index = (index + 1u) & mask;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
/**
|
|
2304
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
2305
|
+
*/
|
|
2306
|
+
float getGammaForColor(vec3 rgb) {
|
|
2307
|
+
return mix(
|
|
2308
|
+
1.25,
|
|
2309
|
+
0.75,
|
|
2310
|
+
// RGB should be linearized but this is good enough for now
|
|
2311
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
2315
|
+
|
|
2316
|
+
// TODO: include the following only in fragment shaders
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Specialized linearstep for doing antialiasing
|
|
2320
|
+
*/
|
|
2321
|
+
float distanceToRatio(float d) {
|
|
2322
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
2326
|
+
if (halfStrokeWidth > 0.0) {
|
|
2327
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
2328
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
2329
|
+
return mix(
|
|
2330
|
+
stroke,
|
|
2331
|
+
d <= 0.0 ? fill : background,
|
|
2332
|
+
distanceToRatio(sd));
|
|
2333
|
+
} else {
|
|
2334
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
|
|
2339
|
+
in highp vec4 vPickingColor;
|
|
2340
|
+
|
|
2341
|
+
|
|
2342
|
+
const lowp vec4 white = vec4(1.0);
|
|
2343
|
+
const lowp vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
|
|
2344
|
+
|
|
2345
|
+
flat in float vRadius;
|
|
2346
|
+
flat in float vRadiusWithPadding;
|
|
2347
|
+
|
|
2348
|
+
flat in lowp vec4 vFillColor;
|
|
2349
|
+
flat in lowp vec4 vStrokeColor;
|
|
2350
|
+
flat in lowp float vShape;
|
|
2351
|
+
flat in lowp float vHalfStrokeWidth;
|
|
2352
|
+
|
|
2353
|
+
flat in mat2 vRotationMatrix;
|
|
2354
|
+
|
|
2355
|
+
out lowp vec4 fragColor;
|
|
2356
|
+
|
|
2357
|
+
// Copypaste from vertex shader
|
|
2358
|
+
const float CIRCLE = 0.0;
|
|
2359
|
+
const float SQUARE = 1.0;
|
|
2360
|
+
const float CROSS = 2.0;
|
|
2361
|
+
const float DIAMOND = 3.0;
|
|
2362
|
+
const float TRIANGLE_UP = 4.0;
|
|
2363
|
+
const float TICK_UP = 8.0;
|
|
2364
|
+
|
|
2365
|
+
// The distance functions are inspired by:
|
|
2366
|
+
// http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
|
2367
|
+
// However, these are not true distance functions, because the corners need to be sharp.
|
|
2368
|
+
|
|
2369
|
+
float circle(vec2 p, float r) {
|
|
2370
|
+
return length(p) - r;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
float square(vec2 p, float r) {
|
|
2374
|
+
p = abs(p);
|
|
2375
|
+
return max(p.x, p.y) - r;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
float tickUp(vec2 p, float r) {
|
|
2379
|
+
float halfR = r * 0.5;
|
|
2380
|
+
p.y += halfR;
|
|
2381
|
+
p = abs(p);
|
|
2382
|
+
return max(p.x - r * 0.15, p.y - halfR);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
float equilateralTriangle(vec2 p, float r) {
|
|
2386
|
+
p.y = -p.y;
|
|
2387
|
+
float k = sqrt(3.0);
|
|
2388
|
+
float kr = k * r;
|
|
2389
|
+
p.y -= kr / 2.0;
|
|
2390
|
+
return max((abs(p.x) * k + p.y) / 2.0, -p.y - kr);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
float crossShape(vec2 p, float r) {
|
|
2394
|
+
p = abs(p);
|
|
2395
|
+
|
|
2396
|
+
vec2 b = vec2(0.4, 1.0) * r;
|
|
2397
|
+
vec2 v = abs(p) - b.xy;
|
|
2398
|
+
vec2 h = abs(p) - b.yx;
|
|
2399
|
+
return min(max(v.x, v.y), max(h.x, h.y));
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
float diamond(vec2 p, float r) {
|
|
2403
|
+
p = abs(p);
|
|
2404
|
+
return (max(abs(p.x - p.y), abs(p.x + p.y)) - r) / sqrt(2.0);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
void main() {
|
|
2408
|
+
float d;
|
|
2409
|
+
|
|
2410
|
+
/** Normalized point coord */
|
|
2411
|
+
vec2 p = vRotationMatrix * (2.0 * gl_PointCoord - 1.0) * vRadiusWithPadding;
|
|
2412
|
+
float r = vRadius;
|
|
2413
|
+
|
|
2414
|
+
// We could also use textures here. Could even be faster, because we have plenty of branching here.
|
|
2415
|
+
if (vShape == CIRCLE) {
|
|
2416
|
+
d = circle(p, r);
|
|
2417
|
+
|
|
2418
|
+
} else if (vShape == SQUARE) {
|
|
2419
|
+
d = square(p, r);
|
|
2420
|
+
|
|
2421
|
+
} else if (vShape == CROSS) {
|
|
2422
|
+
d = crossShape(p, r);
|
|
2423
|
+
|
|
2424
|
+
} else if (vShape == DIAMOND) {
|
|
2425
|
+
d = diamond(p, r);
|
|
2426
|
+
|
|
2427
|
+
} else if (vShape == TRIANGLE_UP) {
|
|
2428
|
+
d = equilateralTriangle(p, r);
|
|
2429
|
+
|
|
2430
|
+
} else if (vShape == TICK_UP) {
|
|
2431
|
+
d = tickUp(p, r);
|
|
2432
|
+
|
|
2433
|
+
} else {
|
|
2434
|
+
d = 0.0;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
if (!uPickingEnabled) {
|
|
2438
|
+
lowp vec4 fillColor = mix(vFillColor, white, -d * uGradientStrength / vRadius);
|
|
2439
|
+
|
|
2440
|
+
fragColor = distanceToColor(
|
|
2441
|
+
d + (uInwardStroke ? vHalfStrokeWidth : 0.0),
|
|
2442
|
+
fillColor,
|
|
2443
|
+
vStrokeColor,
|
|
2444
|
+
vec4(0.0),
|
|
2445
|
+
vHalfStrokeWidth);
|
|
2446
|
+
|
|
2447
|
+
} else if (d - vHalfStrokeWidth <= 0.0) {
|
|
2448
|
+
fragColor = vPickingColor;
|
|
2449
|
+
|
|
2450
|
+
} else {
|
|
2451
|
+
discard;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
",
|
|
2456
|
+
"vertex": "precision highp float;
|
|
2457
|
+
precision highp int;
|
|
2458
|
+
|
|
2459
|
+
// view: viewRoot
|
|
2460
|
+
|
|
2461
|
+
layout(std140) uniform Mark {
|
|
2462
|
+
/**
|
|
2463
|
+
* The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
|
|
2464
|
+
* Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
|
|
2465
|
+
* geometric zoom, etc.
|
|
2466
|
+
*/
|
|
2467
|
+
uniform bool uInwardStroke;
|
|
2468
|
+
|
|
2469
|
+
/** The minimum point size in pixels when rendering into the picking buffer */
|
|
2470
|
+
uniform float uMinPickingSize;
|
|
2471
|
+
|
|
2472
|
+
/** Scale factor for geometric zoom */
|
|
2473
|
+
uniform mediump float uScaleFactor;
|
|
2474
|
+
|
|
2475
|
+
uniform mediump float uZoomLevel;
|
|
2476
|
+
uniform highp float uSemanticThreshold;
|
|
2477
|
+
|
|
2478
|
+
uniform mediump float uGradientStrength;
|
|
2479
|
+
|
|
2480
|
+
mediump float uDomain_x[2];
|
|
2481
|
+
|
|
2482
|
+
mediump float uDomain_y[2];
|
|
2483
|
+
|
|
2484
|
+
|
|
2485
|
+
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
|
|
2489
|
+
#define PI 3.141593
|
|
2490
|
+
|
|
2491
|
+
uniform View {
|
|
2492
|
+
/** Offset in "unit" units */
|
|
2493
|
+
mediump vec2 uViewOffset;
|
|
2494
|
+
mediump vec2 uViewScale;
|
|
2495
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
2496
|
+
mediump vec2 uViewportSize;
|
|
2497
|
+
lowp float uDevicePixelRatio;
|
|
2498
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
2499
|
+
// that is rendered with the specified opacity.
|
|
2500
|
+
lowp float uViewOpacity;
|
|
2501
|
+
bool uPickingEnabled;
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
|
|
2505
|
+
/**
|
|
2506
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
2507
|
+
* (0, 0) is at the bottom left corner.
|
|
2508
|
+
*/
|
|
2509
|
+
vec4 unitToNdc(vec2 coord) {
|
|
2510
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
vec4 unitToNdc(float x, float y) {
|
|
2514
|
+
return unitToNdc(vec2(x, y));
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
2518
|
+
return unitToNdc(coord / uViewportSize);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
2522
|
+
return pixelsToNdc(vec2(x, y));
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
2526
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
2530
|
+
|
|
2531
|
+
highp uint hash32(highp uint key) {
|
|
2532
|
+
highp uint v = key;
|
|
2533
|
+
v ^= v >> 16u;
|
|
2534
|
+
v *= 0x7feb352du;
|
|
2535
|
+
v ^= v >> 15u;
|
|
2536
|
+
v *= 0x846ca68bu;
|
|
2537
|
+
v ^= v >> 16u;
|
|
2538
|
+
return v;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
2542
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
2543
|
+
ivec2 texSize = textureSize(s, 0);
|
|
2544
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
2548
|
+
ivec2 texSize = textureSize(s, 0);
|
|
2549
|
+
highp uint width = uint(texSize.x);
|
|
2550
|
+
highp uint size = width * uint(texSize.y);
|
|
2551
|
+
highp uint mask = size - 1u;
|
|
2552
|
+
highp uint index = hash32(value) & mask;
|
|
2553
|
+
|
|
2554
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
2555
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
2556
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
2557
|
+
if (entry == value) {
|
|
2558
|
+
return true;
|
|
2559
|
+
}
|
|
2560
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
2561
|
+
return false;
|
|
2562
|
+
}
|
|
2563
|
+
index = (index + 1u) & mask;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
return false;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
/**
|
|
2570
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
2571
|
+
*/
|
|
2572
|
+
float getGammaForColor(vec3 rgb) {
|
|
2573
|
+
return mix(
|
|
2574
|
+
1.25,
|
|
2575
|
+
0.75,
|
|
2576
|
+
// RGB should be linearized but this is good enough for now
|
|
2577
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
2581
|
+
|
|
2582
|
+
// TODO: include the following only in fragment shaders
|
|
2583
|
+
|
|
2584
|
+
/**
|
|
2585
|
+
* Specialized linearstep for doing antialiasing
|
|
2586
|
+
*/
|
|
2587
|
+
float distanceToRatio(float d) {
|
|
2588
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
2592
|
+
if (halfStrokeWidth > 0.0) {
|
|
2593
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
2594
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
2595
|
+
return mix(
|
|
2596
|
+
stroke,
|
|
2597
|
+
d <= 0.0 ? fill : background,
|
|
2598
|
+
distanceToRatio(sd));
|
|
2599
|
+
} else {
|
|
2600
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
|
|
2605
|
+
uniform highp float uZero;
|
|
2606
|
+
|
|
2607
|
+
// Utils ------------
|
|
2608
|
+
|
|
2609
|
+
vec3 getDiscreteColor(sampler2D s, int index) {
|
|
2610
|
+
return texelFetch(s, ivec2(index % textureSize(s, 0).x, 0), 0).rgb;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
vec3 getInterpolatedColor(sampler2D s, float unitValue) {
|
|
2614
|
+
return texture(s, vec2(unitValue, 0.0)).rgb;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
float clampToRange(float value, vec2 range) {
|
|
2618
|
+
return clamp(value, min(range[0], range[1]), max(range[0], range[1]));
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// Scales ------------
|
|
2622
|
+
// Based on d3 scales: https://github.com/d3/d3-scale
|
|
2623
|
+
|
|
2624
|
+
float scaleIdentity(float value) {
|
|
2625
|
+
return value;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
float scaleIdentity(uint value) {
|
|
2629
|
+
return float(value);
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
float scaleLinear(float value, vec2 domain, vec2 range) {
|
|
2633
|
+
float domainSpan = domain[1] - domain[0];
|
|
2634
|
+
float rangeSpan = range[1] - range[0];
|
|
2635
|
+
return (value - domain[0]) / domainSpan * rangeSpan + range[0];
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
float scaleLog(float value, vec2 domain, vec2 range, float base) {
|
|
2639
|
+
// y = m log(x) + b
|
|
2640
|
+
// TODO: Perf optimization: precalculate log domain in js.
|
|
2641
|
+
// TODO: Reversed domain, etc
|
|
2642
|
+
return scaleLinear(log(value) / log(base), log(domain) / log(base), range);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
float symlog(float value, float constant) {
|
|
2646
|
+
// WARNING: emulating log1p with log(x + 1). Small numbers are likely to
|
|
2647
|
+
// have significant precision problems.
|
|
2648
|
+
return sign(value) * log(abs(value / constant) + 1.0);
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
float scaleSymlog(float value, vec2 domain, vec2 range, float constant) {
|
|
2652
|
+
return scaleLinear(
|
|
2653
|
+
symlog(value, constant),
|
|
2654
|
+
vec2(symlog(domain[0], constant), symlog(domain[1], constant)),
|
|
2655
|
+
range
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
float scalePow(float value, vec2 domain, vec2 range, float exponent) {
|
|
2660
|
+
// y = mx^k + b
|
|
2661
|
+
// TODO: Perf optimization: precalculate pow domain in js.
|
|
2662
|
+
// TODO: Reversed domain, etc
|
|
2663
|
+
return scaleLinear(
|
|
2664
|
+
pow(abs(value), exponent) * sign(value),
|
|
2665
|
+
pow(abs(domain), vec2(exponent)) * sign(domain),
|
|
2666
|
+
range
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// TODO: scaleThreshold
|
|
2671
|
+
// TODO: scaleQuantile (special case of threshold scale)
|
|
2672
|
+
|
|
2673
|
+
// TODO: domainExtent should be uint
|
|
2674
|
+
float scaleBand(uint value, vec2 domainExtent, vec2 range,
|
|
2675
|
+
float paddingInner, float paddingOuter,
|
|
2676
|
+
float align, float band) {
|
|
2677
|
+
|
|
2678
|
+
// TODO: reverse
|
|
2679
|
+
float start = range[0];
|
|
2680
|
+
float stop = range[1];
|
|
2681
|
+
float rangeSpan = stop - start;
|
|
2682
|
+
|
|
2683
|
+
float n = domainExtent[1] - domainExtent[0];
|
|
2684
|
+
|
|
2685
|
+
// This fix departs from Vega and d3: https://github.com/vega/vega/issues/3357#issuecomment-1063253596
|
|
2686
|
+
paddingInner = int(n) > 1 ? paddingInner : 0.0;
|
|
2687
|
+
|
|
2688
|
+
// Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
|
|
2689
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
2690
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
2691
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
2692
|
+
|
|
2693
|
+
return start + (float(value) - domainExtent[0]) * step + bandwidth * band;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
const int lowBits = 12;
|
|
2697
|
+
const float lowDivisor = pow(2.0, float(lowBits));
|
|
2698
|
+
const uint lowMask = uint(lowDivisor - 1.0);
|
|
2699
|
+
|
|
2700
|
+
vec2 splitUint(uint value) {
|
|
2701
|
+
uint valueLo = value & lowMask;
|
|
2702
|
+
uint valueHi = value - valueLo;
|
|
2703
|
+
return vec2(float(valueHi), float(valueLo));
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/**
|
|
2707
|
+
* High precision variant of scaleBand for index/locus scales
|
|
2708
|
+
*/
|
|
2709
|
+
float scaleBandHp(uint value, vec3 domainExtent, vec2 range,
|
|
2710
|
+
float paddingInner, float paddingOuter,
|
|
2711
|
+
float align, float band) {
|
|
2712
|
+
|
|
2713
|
+
// TODO: reverse
|
|
2714
|
+
float start = range[0];
|
|
2715
|
+
float stop = range[1];
|
|
2716
|
+
float rangeSpan = stop - start;
|
|
2717
|
+
|
|
2718
|
+
vec2 domainStart = domainExtent.xy;
|
|
2719
|
+
float n = domainExtent[2];
|
|
2720
|
+
|
|
2721
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
2722
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
2723
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
2724
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
2725
|
+
|
|
2726
|
+
// Split into to values with each having a reduced number of significant digits
|
|
2727
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
2728
|
+
vec2 splitValue = splitUint(value);
|
|
2729
|
+
|
|
2730
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
2731
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
2732
|
+
// some equivalent form that does premature rounding.
|
|
2733
|
+
float inf = 1.0 / uZero;
|
|
2734
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
2735
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
2736
|
+
|
|
2737
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
/**
|
|
2741
|
+
* High precision variant of scaleBand for index/locus scales for large
|
|
2742
|
+
* domains where 32bit uints are not sufficient to represent the domain.
|
|
2743
|
+
*/
|
|
2744
|
+
float scaleBandHp(uvec2 value, vec3 domainExtent, vec2 range,
|
|
2745
|
+
float paddingInner, float paddingOuter,
|
|
2746
|
+
float align, float band) {
|
|
2747
|
+
|
|
2748
|
+
// TODO: reverse
|
|
2749
|
+
float start = range[0];
|
|
2750
|
+
float stop = range[1];
|
|
2751
|
+
float rangeSpan = stop - start;
|
|
2752
|
+
|
|
2753
|
+
vec2 domainStart = domainExtent.xy;
|
|
2754
|
+
float n = domainExtent[2];
|
|
2755
|
+
|
|
2756
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
2757
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
2758
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
2759
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
2760
|
+
|
|
2761
|
+
// Split into to values with each having a reduced number of significant digits
|
|
2762
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
2763
|
+
vec2 splitValue = vec2(float(value[0]) * lowDivisor, float(value[1]));
|
|
2764
|
+
|
|
2765
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
2766
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
2767
|
+
// some equivalent form that does premature rounding.
|
|
2768
|
+
float inf = 1.0 / uZero;
|
|
2769
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
2770
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
2771
|
+
|
|
2772
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
|
|
2776
|
+
in highp uint attr_uniqueId;
|
|
2777
|
+
in highp float attr_x;
|
|
2778
|
+
in highp float attr_y;
|
|
2779
|
+
in highp uint attr_fill;
|
|
2780
|
+
|
|
2781
|
+
|
|
2782
|
+
uint accessor_uniqueId_0() {
|
|
2783
|
+
return attr_uniqueId;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
uint getScaled_uniqueId() {
|
|
2787
|
+
return accessor_uniqueId_0();
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
#define uniqueId_DEFINED
|
|
2791
|
+
|
|
2792
|
+
|
|
2793
|
+
float accessor_x_0() {
|
|
2794
|
+
return attr_x;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
|
|
2798
|
+
//////////////////////////////////////////////////////////////////////
|
|
2799
|
+
// Channel: x
|
|
2800
|
+
|
|
2801
|
+
const vec2 range_x = vec2(0.0, 1.0);
|
|
2802
|
+
|
|
2803
|
+
float scale_x(float value) {
|
|
2804
|
+
int slot = 0;
|
|
2805
|
+
vec2 domain = vec2(uDomain_x[slot], uDomain_x[slot + 1]);
|
|
2806
|
+
float transformed = scaleLinear(value, domain, range_x);
|
|
2807
|
+
return transformed;
|
|
2808
|
+
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
float getScaled_x() {
|
|
2812
|
+
return scale_x(accessor_x_0());
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
#define x_DEFINED
|
|
2816
|
+
|
|
2817
|
+
|
|
2818
|
+
float accessor_y_0() {
|
|
2819
|
+
return attr_y;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
|
|
2823
|
+
//////////////////////////////////////////////////////////////////////
|
|
2824
|
+
// Channel: y
|
|
2825
|
+
|
|
2826
|
+
const vec2 range_y = vec2(0.0, 1.0);
|
|
2827
|
+
|
|
2828
|
+
float scale_y(float value) {
|
|
2829
|
+
int slot = 0;
|
|
2830
|
+
vec2 domain = vec2(uDomain_y[slot], uDomain_y[slot + 1]);
|
|
2831
|
+
float transformed = scaleLinear(value, domain, range_y);
|
|
2832
|
+
return transformed;
|
|
2833
|
+
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
float getScaled_y() {
|
|
2837
|
+
return scale_y(accessor_y_0());
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
#define y_DEFINED
|
|
2841
|
+
|
|
2842
|
+
|
|
2843
|
+
float accessor_size_0() {
|
|
2844
|
+
// Constant value
|
|
2845
|
+
return float(100.0);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
float getScaled_size() {
|
|
2849
|
+
return accessor_size_0();
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
#define size_DEFINED
|
|
2853
|
+
|
|
2854
|
+
|
|
2855
|
+
float accessor_semanticScore_0() {
|
|
2856
|
+
// Constant value
|
|
2857
|
+
return float(0.0);
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
float getScaled_semanticScore() {
|
|
2861
|
+
return accessor_semanticScore_0();
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
#define semanticScore_DEFINED
|
|
2865
|
+
|
|
2866
|
+
|
|
2867
|
+
float accessor_shape_0() {
|
|
2868
|
+
// Constant value
|
|
2869
|
+
return float(0.0);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
float getScaled_shape() {
|
|
2873
|
+
return accessor_shape_0();
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
#define shape_DEFINED
|
|
2877
|
+
|
|
2878
|
+
|
|
2879
|
+
float accessor_strokeWidth_0() {
|
|
2880
|
+
// Constant value
|
|
2881
|
+
return float(0.0);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
float getScaled_strokeWidth() {
|
|
2885
|
+
return accessor_strokeWidth_0();
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
#define strokeWidth_DEFINED
|
|
2889
|
+
|
|
2890
|
+
|
|
2891
|
+
float accessor_dx_0() {
|
|
2892
|
+
// Constant value
|
|
2893
|
+
return float(0.0);
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
float getScaled_dx() {
|
|
2897
|
+
return accessor_dx_0();
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
#define dx_DEFINED
|
|
2901
|
+
|
|
2902
|
+
|
|
2903
|
+
float accessor_dy_0() {
|
|
2904
|
+
// Constant value
|
|
2905
|
+
return float(0.0);
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
float getScaled_dy() {
|
|
2909
|
+
return accessor_dy_0();
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
#define dy_DEFINED
|
|
2913
|
+
|
|
2914
|
+
|
|
2915
|
+
float accessor_angle_0() {
|
|
2916
|
+
// Constant value
|
|
2917
|
+
return float(0.0);
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
float getScaled_angle() {
|
|
2921
|
+
return accessor_angle_0();
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
#define angle_DEFINED
|
|
2925
|
+
|
|
2926
|
+
|
|
2927
|
+
vec3 accessor_stroke_0() {
|
|
2928
|
+
// Constant value
|
|
2929
|
+
return vec3(0.0, 0.0, 0.0);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
vec3 getScaled_stroke() {
|
|
2933
|
+
return accessor_stroke_0();
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
#define stroke_DEFINED
|
|
2937
|
+
|
|
2938
|
+
|
|
2939
|
+
float accessor_strokeOpacity_0() {
|
|
2940
|
+
// Constant value
|
|
2941
|
+
return float(1.0);
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
float getScaled_strokeOpacity() {
|
|
2945
|
+
return accessor_strokeOpacity_0();
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
#define strokeOpacity_DEFINED
|
|
2949
|
+
|
|
2950
|
+
|
|
2951
|
+
uint accessor_fill_0() {
|
|
2952
|
+
return attr_fill;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
|
|
2956
|
+
//////////////////////////////////////////////////////////////////////
|
|
2957
|
+
// Channel: fill
|
|
2958
|
+
|
|
2959
|
+
uniform sampler2D uRangeTexture_fill;
|
|
2960
|
+
|
|
2961
|
+
vec3 scale_fill(uint value) {
|
|
2962
|
+
int slot = 0;
|
|
2963
|
+
float transformed = scaleIdentity(value);
|
|
2964
|
+
return getDiscreteColor(uRangeTexture_fill, int(transformed));
|
|
2965
|
+
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
vec3 getScaled_fill() {
|
|
2969
|
+
return scale_fill(accessor_fill_0());
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
#define fill_DEFINED
|
|
2973
|
+
|
|
2974
|
+
|
|
2975
|
+
float accessor_fillOpacity_0() {
|
|
2976
|
+
// Constant value
|
|
2977
|
+
return float(1.0);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
float getScaled_fillOpacity() {
|
|
2981
|
+
return accessor_fillOpacity_0();
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
#define fillOpacity_DEFINED
|
|
2985
|
+
|
|
2986
|
+
bool isPointSelected() {
|
|
2987
|
+
return false;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
|
|
2991
|
+
/**
|
|
2992
|
+
* Describes where a sample facet should be shown. Interpolating between the
|
|
2993
|
+
* current and target positions/heights allows for transitioning between facet
|
|
2994
|
+
* configurations.
|
|
2995
|
+
*/
|
|
2996
|
+
struct SampleFacetPosition {
|
|
2997
|
+
float pos;
|
|
2998
|
+
float height;
|
|
2999
|
+
float targetPos;
|
|
3000
|
+
float targetHeight;
|
|
3001
|
+
};
|
|
3002
|
+
|
|
3003
|
+
/**
|
|
3004
|
+
* Trasition fraction [0, 1] between the current and target configurations.
|
|
3005
|
+
*/
|
|
3006
|
+
uniform float uTransitionOffset;
|
|
3007
|
+
|
|
3008
|
+
|
|
3009
|
+
// ----------------------------------------------------------------------------
|
|
3010
|
+
|
|
3011
|
+
#if !defined(SAMPLE_FACET_UNIFORM) && !defined(SAMPLE_FACET_TEXTURE)
|
|
3012
|
+
|
|
3013
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
3014
|
+
return SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
#elif defined(SAMPLE_FACET_UNIFORM)
|
|
3018
|
+
|
|
3019
|
+
/**
|
|
3020
|
+
* Location and height of the band on the Y axis on a normalized [0, 1] scale.
|
|
3021
|
+
* Elements: curr pos, curr height, target pos, target height
|
|
3022
|
+
*/
|
|
3023
|
+
uniform vec4 uSampleFacet;
|
|
3024
|
+
|
|
3025
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
3026
|
+
return SampleFacetPosition(
|
|
3027
|
+
1.0 - uSampleFacet.x - uSampleFacet.y,
|
|
3028
|
+
uSampleFacet.y,
|
|
3029
|
+
1.0 - uSampleFacet.z - uSampleFacet.w,
|
|
3030
|
+
uSampleFacet.w
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
#elif defined(SAMPLE_FACET_TEXTURE)
|
|
3035
|
+
|
|
3036
|
+
uniform sampler2D uSampleFacetTexture;
|
|
3037
|
+
|
|
3038
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
3039
|
+
vec4 texel = texelFetch(uSampleFacetTexture, ivec2(int(attr_facetIndex), 0), 0);
|
|
3040
|
+
return SampleFacetPosition(
|
|
3041
|
+
1.0 - texel.r - texel.g,
|
|
3042
|
+
texel.g,
|
|
3043
|
+
1.0 - texel.r - texel.g,
|
|
3044
|
+
texel.g);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
#endif
|
|
3048
|
+
|
|
3049
|
+
// ----------------------------------------------------------------------------
|
|
3050
|
+
|
|
3051
|
+
bool isFacetedSamples(SampleFacetPosition facetPos) {
|
|
3052
|
+
return facetPos != SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
bool isFacetedSamples() {
|
|
3056
|
+
return isFacetedSamples(getSampleFacetPos());
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
bool isInTransit() {
|
|
3060
|
+
return uTransitionOffset > 0.0;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
float getTransitionFraction(float xPos) {
|
|
3064
|
+
return smoothstep(0.0, 0.7 + uTransitionOffset, (xPos - uTransitionOffset) * 2.0);
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
vec2 applySampleFacet(vec2 pos) {
|
|
3068
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
3069
|
+
|
|
3070
|
+
if (!isFacetedSamples(facetPos)) {
|
|
3071
|
+
return pos;
|
|
3072
|
+
} else if (isInTransit()) {
|
|
3073
|
+
vec2 interpolated = mix(
|
|
3074
|
+
vec2(facetPos.pos, facetPos.height),
|
|
3075
|
+
vec2(facetPos.targetPos, facetPos.targetHeight),
|
|
3076
|
+
getTransitionFraction(pos.x));
|
|
3077
|
+
return vec2(pos.x, interpolated[0] + pos.y * interpolated[1]);
|
|
3078
|
+
} else {
|
|
3079
|
+
return vec2(pos.x, facetPos.pos + pos.y * facetPos.height);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
float getSampleFacetHeight(vec2 pos) {
|
|
3084
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
3085
|
+
|
|
3086
|
+
if (!isFacetedSamples(facetPos)) {
|
|
3087
|
+
return 1.0;
|
|
3088
|
+
} else if (isInTransit()) {
|
|
3089
|
+
return mix(
|
|
3090
|
+
facetPos.height,
|
|
3091
|
+
facetPos.targetHeight,
|
|
3092
|
+
getTransitionFraction(pos.x));
|
|
3093
|
+
} else {
|
|
3094
|
+
return facetPos.height;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
|
|
3099
|
+
/*
|
|
3100
|
+
* Based on concepts presented at:
|
|
3101
|
+
* https://webglfundamentals.org/webgl/lessons/webgl-picking.html
|
|
3102
|
+
* https://deck.gl/docs/developer-guide/custom-layers/picking
|
|
3103
|
+
*/
|
|
3104
|
+
|
|
3105
|
+
out highp vec4 vPickingColor;
|
|
3106
|
+
|
|
3107
|
+
/**
|
|
3108
|
+
* Passes the unique id to the fragment shader as a color if picking is enabled.
|
|
3109
|
+
* Returns true if picking is enabled.
|
|
3110
|
+
*/
|
|
3111
|
+
bool setupPicking() {
|
|
3112
|
+
if (uPickingEnabled) {
|
|
3113
|
+
#ifdef uniqueId_DEFINED
|
|
3114
|
+
uint id = attr_uniqueId;
|
|
3115
|
+
vPickingColor = vec4(
|
|
3116
|
+
ivec4(id >> 0, id >> 8, id >> 16, id >> 24) & 0xFF
|
|
3117
|
+
) / float(0xFF);
|
|
3118
|
+
#else
|
|
3119
|
+
vPickingColor = vec4(1.0);
|
|
3120
|
+
#endif
|
|
3121
|
+
return true;
|
|
3122
|
+
}
|
|
3123
|
+
return false;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
|
|
3127
|
+
flat out float vRadius;
|
|
3128
|
+
flat out float vRadiusWithPadding;
|
|
3129
|
+
flat out lowp vec4 vFillColor;
|
|
3130
|
+
flat out lowp vec4 vStrokeColor;
|
|
3131
|
+
flat out lowp float vShape;
|
|
3132
|
+
flat out lowp float vHalfStrokeWidth;
|
|
3133
|
+
flat out mat2 vRotationMatrix;
|
|
3134
|
+
|
|
3135
|
+
// Copypaste from fragment shader
|
|
3136
|
+
const float CIRCLE = 0.0;
|
|
3137
|
+
const float SQUARE = 1.0;
|
|
3138
|
+
const float CROSS = 2.0;
|
|
3139
|
+
const float DIAMOND = 3.0;
|
|
3140
|
+
const float TRIANGLE_UP = 4.0;
|
|
3141
|
+
const float TRIANGLE_RIGHT = 5.0;
|
|
3142
|
+
const float TRIANGLE_DOWN = 6.0;
|
|
3143
|
+
const float TRIANGLE_LEFT = 7.0;
|
|
3144
|
+
const float TICK_UP = 8.0;
|
|
3145
|
+
const float TICK_RIGHT = 9.0;
|
|
3146
|
+
const float TICK_DOWN = 10.0;
|
|
3147
|
+
const float TICK_LEFT = 11.0;
|
|
3148
|
+
|
|
3149
|
+
float computeSemanticThresholdFactor() {
|
|
3150
|
+
// TODO: add smooth transition
|
|
3151
|
+
return getScaled_semanticScore() >= uSemanticThreshold ? 1.0 : 0.0;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// TODO: Move this into common.glsl or something
|
|
3155
|
+
vec2 getDxDy() {
|
|
3156
|
+
#if defined(dx_DEFINED) || defined(dy_DEFINED)
|
|
3157
|
+
return vec2(getScaled_dx(), getScaled_dy()) / uViewportSize;
|
|
3158
|
+
#else
|
|
3159
|
+
return vec2(0.0, 0.0);
|
|
3160
|
+
#endif
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
void main(void) {
|
|
3164
|
+
float shapeAngle = 0.0;
|
|
3165
|
+
|
|
3166
|
+
// Selected points should always be visible
|
|
3167
|
+
float semanticThresholdFactor = isPointSelected()
|
|
3168
|
+
? 1.0
|
|
3169
|
+
: computeSemanticThresholdFactor();
|
|
3170
|
+
|
|
3171
|
+
if (semanticThresholdFactor <= 0.0) {
|
|
3172
|
+
gl_PointSize = 0.0;
|
|
3173
|
+
// Place the vertex outside the viewport. The default (0, 0) makes this super-slow
|
|
3174
|
+
// on Apple Silicon. Probably related to the tile-based GPU architecture.
|
|
3175
|
+
gl_Position = vec4(100.0, 0.0, 0.0, 0.0);
|
|
3176
|
+
// Exit early. MAY prevent some unnecessary calculations.
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
float size = getScaled_size();
|
|
3181
|
+
vec2 pos = vec2(getScaled_x(), getScaled_y()) + getDxDy();
|
|
3182
|
+
|
|
3183
|
+
gl_Position = unitToNdc(applySampleFacet(pos));
|
|
3184
|
+
|
|
3185
|
+
float strokeWidth = getScaled_strokeWidth();
|
|
3186
|
+
|
|
3187
|
+
float diameter = sqrt(size) *
|
|
3188
|
+
uScaleFactor *
|
|
3189
|
+
semanticThresholdFactor;
|
|
3190
|
+
|
|
3191
|
+
// Clamp minimum size and adjust opacity instead. Yields more pleasing result,
|
|
3192
|
+
// no flickering etc.
|
|
3193
|
+
float opacity = uViewOpacity;
|
|
3194
|
+
if (strokeWidth <= 0.0 || uInwardStroke) {
|
|
3195
|
+
float minDiameter = 1.0 / uDevicePixelRatio;
|
|
3196
|
+
if (diameter < minDiameter) {
|
|
3197
|
+
// We do some "cheap" gamma correction here. It breaks on dark background, though.
|
|
3198
|
+
// First we take a square of the size and then apply "gamma" of 1.5.
|
|
3199
|
+
opacity *= pow(diameter / minDiameter, 2.5);
|
|
3200
|
+
diameter = minDiameter;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
float fillOpa = getScaled_fillOpacity() * opacity;
|
|
3205
|
+
float strokeOpa = getScaled_strokeOpacity() * opacity;
|
|
3206
|
+
|
|
3207
|
+
vShape = getScaled_shape();
|
|
3208
|
+
|
|
3209
|
+
// Circle doesn't have sharp corners. Do some special optimizations to minimize the point size.
|
|
3210
|
+
bool circle = vShape == 0.0;
|
|
3211
|
+
|
|
3212
|
+
if (vShape > TICK_UP && vShape <= TICK_LEFT) {
|
|
3213
|
+
shapeAngle = (vShape - TICK_UP) * 90.0;
|
|
3214
|
+
vShape = TICK_UP;
|
|
3215
|
+
} else if (vShape > TRIANGLE_UP && vShape <= TRIANGLE_LEFT) {
|
|
3216
|
+
shapeAngle = (vShape - TRIANGLE_UP) * 90.0;
|
|
3217
|
+
vShape = TRIANGLE_UP;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
float angleInDegrees = getScaled_angle();
|
|
3221
|
+
float angle = -(shapeAngle + angleInDegrees) * PI / 180.0;
|
|
3222
|
+
float sinTheta = sin(angle);
|
|
3223
|
+
float cosTheta = cos(angle);
|
|
3224
|
+
vRotationMatrix = mat2(cosTheta, sinTheta, -sinTheta, cosTheta);
|
|
3225
|
+
|
|
3226
|
+
// Not needed if we would draw rotated quads instead of gl.POINTS
|
|
3227
|
+
float roomForRotation = circle ? 1.0 : sin(mod(angle, PI / 2.0) + PI / 4.0) / sin(PI / 4.0);
|
|
3228
|
+
|
|
3229
|
+
float aaPadding = 1.0 / uDevicePixelRatio;
|
|
3230
|
+
float rotationPadding = (diameter * roomForRotation) - diameter;
|
|
3231
|
+
// sqrt(3.0) ensures that the angles of equilateral triangles have enough room
|
|
3232
|
+
float strokePadding = uInwardStroke ? 0.0 : strokeWidth * (circle ? 1.0 : sqrt(3.0));
|
|
3233
|
+
float padding = rotationPadding + strokePadding + aaPadding;
|
|
3234
|
+
|
|
3235
|
+
gl_PointSize = max(
|
|
3236
|
+
(diameter + padding),
|
|
3237
|
+
uPickingEnabled ? uMinPickingSize : 0.0
|
|
3238
|
+
) * uDevicePixelRatio;
|
|
3239
|
+
|
|
3240
|
+
vRadius = diameter / 2.0;
|
|
3241
|
+
vRadiusWithPadding = vRadius + padding / 2.0;
|
|
3242
|
+
|
|
3243
|
+
vHalfStrokeWidth = strokeWidth / 2.0;
|
|
3244
|
+
|
|
3245
|
+
vFillColor = vec4(getScaled_fill() * fillOpa, fillOpa);
|
|
3246
|
+
vStrokeColor = vec4(getScaled_stroke() * strokeOpa, strokeOpa);
|
|
3247
|
+
|
|
3248
|
+
setupPicking();
|
|
3249
|
+
}
|
|
3250
|
+
",
|
|
3251
|
+
}
|
|
3252
|
+
`;
|
|
3253
|
+
|
|
3254
|
+
exports[`generated shader snapshots > point selection example 1`] = `
|
|
3255
|
+
{
|
|
3256
|
+
"fragment": "precision highp float;
|
|
3257
|
+
precision highp int;
|
|
3258
|
+
|
|
3259
|
+
// view: viewRoot
|
|
3260
|
+
|
|
3261
|
+
layout(std140) uniform Mark {
|
|
3262
|
+
/** Minimum size (width, height) of the displayed rectangle in pixels */
|
|
3263
|
+
uniform float uMinWidth;
|
|
3264
|
+
uniform float uMinHeight;
|
|
3265
|
+
|
|
3266
|
+
/** Minimum opacity for the size clamping */
|
|
3267
|
+
uniform float uMinOpacity;
|
|
3268
|
+
|
|
3269
|
+
uniform float uCornerRadiusTopRight;
|
|
3270
|
+
uniform float uCornerRadiusBottomRight;
|
|
3271
|
+
uniform float uCornerRadiusTopLeft;
|
|
3272
|
+
uniform float uCornerRadiusBottomLeft;
|
|
3273
|
+
|
|
3274
|
+
uniform int uHatchPattern;
|
|
3275
|
+
|
|
3276
|
+
uniform vec3 uShadowColor;
|
|
3277
|
+
uniform float uShadowOpacity;
|
|
3278
|
+
uniform float uShadowBlur;
|
|
3279
|
+
uniform float uShadowOffsetX;
|
|
3280
|
+
uniform float uShadowOffsetY;
|
|
3281
|
+
|
|
3282
|
+
// Selection parameter
|
|
3283
|
+
uniform highp uint uParam_highlight;
|
|
3284
|
+
mediump float uDomain_x[2];
|
|
3285
|
+
|
|
3286
|
+
mediump float uDomain_y[2];
|
|
3287
|
+
|
|
3288
|
+
|
|
3289
|
+
|
|
3290
|
+
uniform highp float attr_y2;
|
|
3291
|
+
|
|
3292
|
+
|
|
3293
|
+
};
|
|
3294
|
+
|
|
3295
|
+
|
|
3296
|
+
#define STROKED
|
|
3297
|
+
|
|
3298
|
+
#define PI 3.141593
|
|
3299
|
+
|
|
3300
|
+
uniform View {
|
|
3301
|
+
/** Offset in "unit" units */
|
|
3302
|
+
mediump vec2 uViewOffset;
|
|
3303
|
+
mediump vec2 uViewScale;
|
|
3304
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
3305
|
+
mediump vec2 uViewportSize;
|
|
3306
|
+
lowp float uDevicePixelRatio;
|
|
3307
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
3308
|
+
// that is rendered with the specified opacity.
|
|
3309
|
+
lowp float uViewOpacity;
|
|
3310
|
+
bool uPickingEnabled;
|
|
3311
|
+
};
|
|
3312
|
+
|
|
3313
|
+
|
|
3314
|
+
/**
|
|
3315
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
3316
|
+
* (0, 0) is at the bottom left corner.
|
|
3317
|
+
*/
|
|
3318
|
+
vec4 unitToNdc(vec2 coord) {
|
|
3319
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
vec4 unitToNdc(float x, float y) {
|
|
3323
|
+
return unitToNdc(vec2(x, y));
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
3327
|
+
return unitToNdc(coord / uViewportSize);
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
3331
|
+
return pixelsToNdc(vec2(x, y));
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
3335
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
3339
|
+
|
|
3340
|
+
highp uint hash32(highp uint key) {
|
|
3341
|
+
highp uint v = key;
|
|
3342
|
+
v ^= v >> 16u;
|
|
3343
|
+
v *= 0x7feb352du;
|
|
3344
|
+
v ^= v >> 15u;
|
|
3345
|
+
v *= 0x846ca68bu;
|
|
3346
|
+
v ^= v >> 16u;
|
|
3347
|
+
return v;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
3351
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
3352
|
+
ivec2 texSize = textureSize(s, 0);
|
|
3353
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
3357
|
+
ivec2 texSize = textureSize(s, 0);
|
|
3358
|
+
highp uint width = uint(texSize.x);
|
|
3359
|
+
highp uint size = width * uint(texSize.y);
|
|
3360
|
+
highp uint mask = size - 1u;
|
|
3361
|
+
highp uint index = hash32(value) & mask;
|
|
3362
|
+
|
|
3363
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
3364
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
3365
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
3366
|
+
if (entry == value) {
|
|
3367
|
+
return true;
|
|
3368
|
+
}
|
|
3369
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
index = (index + 1u) & mask;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
return false;
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
/**
|
|
3379
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
3380
|
+
*/
|
|
3381
|
+
float getGammaForColor(vec3 rgb) {
|
|
3382
|
+
return mix(
|
|
3383
|
+
1.25,
|
|
3384
|
+
0.75,
|
|
3385
|
+
// RGB should be linearized but this is good enough for now
|
|
3386
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
3390
|
+
|
|
3391
|
+
// TODO: include the following only in fragment shaders
|
|
3392
|
+
|
|
3393
|
+
/**
|
|
3394
|
+
* Specialized linearstep for doing antialiasing
|
|
3395
|
+
*/
|
|
3396
|
+
float distanceToRatio(float d) {
|
|
3397
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
3401
|
+
if (halfStrokeWidth > 0.0) {
|
|
3402
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
3403
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
3404
|
+
return mix(
|
|
3405
|
+
stroke,
|
|
3406
|
+
d <= 0.0 ? fill : background,
|
|
3407
|
+
distanceToRatio(sd));
|
|
3408
|
+
} else {
|
|
3409
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
|
|
3414
|
+
in highp vec4 vPickingColor;
|
|
3415
|
+
|
|
3416
|
+
|
|
3417
|
+
#if defined(ROUNDED_CORNERS) || defined(STROKED) || defined(SHADOW)
|
|
3418
|
+
in vec2 vPosInPixels;
|
|
3419
|
+
#endif
|
|
3420
|
+
|
|
3421
|
+
flat in vec2 vHalfSizeInPixels;
|
|
3422
|
+
|
|
3423
|
+
flat in lowp vec4 vFillColor;
|
|
3424
|
+
flat in lowp vec4 vStrokeColor;
|
|
3425
|
+
flat in float vHalfStrokeWidth;
|
|
3426
|
+
flat in vec4 vCornerRadii;
|
|
3427
|
+
|
|
3428
|
+
out lowp vec4 fragColor;
|
|
3429
|
+
|
|
3430
|
+
// ----------------------------------------------------------------------------
|
|
3431
|
+
// Shadow source: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
|
|
3432
|
+
// License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/)
|
|
3433
|
+
|
|
3434
|
+
#ifdef SHADOW
|
|
3435
|
+
|
|
3436
|
+
// A standard gaussian function, used for weighting samples
|
|
3437
|
+
float gaussian(float x, float sigma) {
|
|
3438
|
+
const float pi = 3.141592653589793;
|
|
3439
|
+
return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// This approximates the error function, needed for the gaussian integral
|
|
3443
|
+
vec2 erf(vec2 x) {
|
|
3444
|
+
vec2 s = sign(x), a = abs(x);
|
|
3445
|
+
x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
|
|
3446
|
+
x *= x;
|
|
3447
|
+
return s - s / (x * x);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
// Return the blurred mask along the x dimension
|
|
3451
|
+
float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
|
|
3452
|
+
float delta = min(halfSize.y - corner - abs(y), 0.0);
|
|
3453
|
+
float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
|
|
3454
|
+
vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
|
|
3455
|
+
return integral.y - integral.x;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// Return the mask for the shadow of a box from lower to upper
|
|
3459
|
+
float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner) {
|
|
3460
|
+
// Center everything to make the math easier
|
|
3461
|
+
vec2 center = (lower + upper) * 0.5;
|
|
3462
|
+
vec2 halfSize = (upper - lower) * 0.5;
|
|
3463
|
+
point -= center;
|
|
3464
|
+
|
|
3465
|
+
// The signal is only non-zero in a limited range, so don't waste samples
|
|
3466
|
+
float low = point.y - halfSize.y;
|
|
3467
|
+
float high = point.y + halfSize.y;
|
|
3468
|
+
float start = clamp(-3.0 * sigma, low, high);
|
|
3469
|
+
float end = clamp(3.0 * sigma, low, high);
|
|
3470
|
+
|
|
3471
|
+
// Accumulate samples (we can get away with surprisingly few samples)
|
|
3472
|
+
float step = (end - start) / 4.0;
|
|
3473
|
+
float y = start + step * 0.5;
|
|
3474
|
+
float value = 0.0;
|
|
3475
|
+
for (int i = 0; i < 4; i++) {
|
|
3476
|
+
value += roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize) * gaussian(y, sigma) * step;
|
|
3477
|
+
y += step;
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
return value;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// ----------------------------------------------------------------------------
|
|
3484
|
+
|
|
3485
|
+
#endif
|
|
3486
|
+
|
|
3487
|
+
// Source: https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
|
3488
|
+
float sdRoundedBox(vec2 p, vec2 b, vec4 r) {
|
|
3489
|
+
r.xy = p.x > 0.0 ? r.xy : r.zw;
|
|
3490
|
+
r.x = p.y > 0.0 ? r.x : r.y;
|
|
3491
|
+
vec2 q = abs(p) - b + r.x;
|
|
3492
|
+
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// Not a true SDF. Makes the corners of strokes sharp and is faster.
|
|
3496
|
+
float sdSharpBox(vec2 p, vec2 b) {
|
|
3497
|
+
vec2 q = abs(p) - b;
|
|
3498
|
+
return max(q.x, q.y);
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
float diagonalPattern(vec2 uv, float spacing) {
|
|
3502
|
+
// Using 1.5 to approximate sqrt(2.0) to reduce aliasing artifacts.
|
|
3503
|
+
float divisor = spacing * vHalfStrokeWidth * 2.0 * 1.5;
|
|
3504
|
+
return abs(mod(uv.x - uv.y, divisor) - 0.5 * divisor) / 1.5;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
float verticalPattern(float x, float spacing) {
|
|
3508
|
+
float divisor = spacing * vHalfStrokeWidth * 2.0;
|
|
3509
|
+
return abs(mod(x, divisor)) / 2.0;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
float circle(vec2 p, float r) {
|
|
3513
|
+
return length(p) - r;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
float masonryCirclePattern(vec2 uv, float spacing, float radius) {
|
|
3517
|
+
float halfSpacing = 0.5 * spacing;
|
|
3518
|
+
|
|
3519
|
+
float row = floor(uv.y / spacing);
|
|
3520
|
+
float shift = mod(row, 2.0) * halfSpacing;
|
|
3521
|
+
|
|
3522
|
+
vec2 shifted = vec2(uv.x + shift, uv.y + halfSpacing);
|
|
3523
|
+
vec2 cell = mod(shifted + 0.5 * spacing, spacing) - halfSpacing;
|
|
3524
|
+
|
|
3525
|
+
return abs(circle(cell, radius));
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
/**
|
|
3529
|
+
* Patterns:
|
|
3530
|
+
* 0 none
|
|
3531
|
+
* 1 diagonal (/)
|
|
3532
|
+
* 2 antiDiagonal (\\)
|
|
3533
|
+
* 3 cross (X)
|
|
3534
|
+
* 4 vertical (|)
|
|
3535
|
+
* 5 horizontal (-)
|
|
3536
|
+
* 6 grid (+)
|
|
3537
|
+
* 7 dots (.)
|
|
3538
|
+
* 8 rings (o)
|
|
3539
|
+
* 9 ringsLarge (O)
|
|
3540
|
+
*/
|
|
3541
|
+
float pattern() {
|
|
3542
|
+
#ifdef STROKED
|
|
3543
|
+
int patternType = uHatchPattern;
|
|
3544
|
+
vec2 uv = vPosInPixels;
|
|
3545
|
+
float spacing = 4.0;
|
|
3546
|
+
|
|
3547
|
+
switch (patternType) {
|
|
3548
|
+
case 1:
|
|
3549
|
+
return diagonalPattern(vec2(uv.x, -uv.y), spacing);
|
|
3550
|
+
case 2:
|
|
3551
|
+
return diagonalPattern(uv, spacing);
|
|
3552
|
+
case 3:
|
|
3553
|
+
return min(
|
|
3554
|
+
diagonalPattern(uv, spacing),
|
|
3555
|
+
diagonalPattern(vec2(uv.x, -uv.y), spacing)
|
|
3556
|
+
);
|
|
3557
|
+
case 4:
|
|
3558
|
+
return verticalPattern(uv.x, spacing);
|
|
3559
|
+
case 5:
|
|
3560
|
+
return verticalPattern(uv.y, spacing);
|
|
3561
|
+
case 6:
|
|
3562
|
+
return min(
|
|
3563
|
+
verticalPattern(uv.x, spacing),
|
|
3564
|
+
verticalPattern(uv.y, spacing)
|
|
3565
|
+
);
|
|
3566
|
+
case 7:
|
|
3567
|
+
case 8:
|
|
3568
|
+
case 9: {
|
|
3569
|
+
float spacing = vHalfStrokeWidth * 14.0;
|
|
3570
|
+
float radius = spacing * (
|
|
3571
|
+
patternType == 8 ? 0.2 :
|
|
3572
|
+
patternType == 9 ? 0.35 :
|
|
3573
|
+
0.07
|
|
3574
|
+
);
|
|
3575
|
+
return masonryCirclePattern(uv, spacing, radius);
|
|
3576
|
+
}
|
|
3577
|
+
default:
|
|
3578
|
+
break;
|
|
3579
|
+
}
|
|
3580
|
+
#endif
|
|
3581
|
+
return 1.0 / 0.0; // Infinity
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
void main(void) {
|
|
3585
|
+
|
|
3586
|
+
#if defined(ROUNDED_CORNERS) || defined(STROKED) || defined(SHADOW)
|
|
3587
|
+
#ifdef ROUNDED_CORNERS
|
|
3588
|
+
// Distance from rectangle's edge in pixels. Negative inside the rectangle.
|
|
3589
|
+
float d = sdRoundedBox(vPosInPixels, vHalfSizeInPixels, vCornerRadii);
|
|
3590
|
+
#else
|
|
3591
|
+
float d = sdSharpBox(vPosInPixels, vHalfSizeInPixels);
|
|
3592
|
+
#endif
|
|
3593
|
+
|
|
3594
|
+
vec4 backgroundColor = vec4(0.0, 0.0, 0.0, 0.0);
|
|
3595
|
+
|
|
3596
|
+
#ifdef SHADOW
|
|
3597
|
+
float maxCornerRadius = max(vCornerRadii.x, max(vCornerRadii.y, max(vCornerRadii.z, vCornerRadii.w)));
|
|
3598
|
+
|
|
3599
|
+
float shadow = 0.0;
|
|
3600
|
+
// Only calculate shadow for the region outside the stroke.
|
|
3601
|
+
if (d >= vHalfStrokeWidth - 1.0 && uShadowOpacity > 0.0) {
|
|
3602
|
+
shadow = roundedBoxShadow(
|
|
3603
|
+
-vHalfSizeInPixels - vHalfStrokeWidth,
|
|
3604
|
+
vHalfSizeInPixels + vHalfStrokeWidth,
|
|
3605
|
+
vPosInPixels - vec2(uShadowOffsetX, -uShadowOffsetY),
|
|
3606
|
+
max(uShadowBlur / 2.5, 0.25),
|
|
3607
|
+
maxCornerRadius + vHalfStrokeWidth
|
|
3608
|
+
) * uShadowOpacity * uViewOpacity;
|
|
3609
|
+
}
|
|
3610
|
+
backgroundColor = vec4(uShadowColor * shadow, shadow);
|
|
3611
|
+
#endif
|
|
3612
|
+
|
|
3613
|
+
if (vHalfStrokeWidth > 0.0 && uHatchPattern > 0) {
|
|
3614
|
+
d = max(d, -pattern());
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
fragColor = distanceToColor(
|
|
3618
|
+
d,
|
|
3619
|
+
vFillColor,
|
|
3620
|
+
vStrokeColor,
|
|
3621
|
+
backgroundColor,
|
|
3622
|
+
vHalfStrokeWidth
|
|
3623
|
+
);
|
|
3624
|
+
|
|
3625
|
+
if (uPickingEnabled) {
|
|
3626
|
+
if (d < vHalfStrokeWidth) {
|
|
3627
|
+
fragColor = vPickingColor;
|
|
3628
|
+
}
|
|
3629
|
+
} else if (fragColor.a == 0.0) {
|
|
3630
|
+
discard;
|
|
3631
|
+
}
|
|
3632
|
+
#else
|
|
3633
|
+
// The trivial, non-decorated case
|
|
3634
|
+
fragColor = vFillColor;
|
|
3635
|
+
if (uPickingEnabled) {
|
|
3636
|
+
fragColor = vPickingColor;
|
|
3637
|
+
}
|
|
3638
|
+
#endif
|
|
3639
|
+
}
|
|
3640
|
+
",
|
|
3641
|
+
"vertex": "precision highp float;
|
|
3642
|
+
precision highp int;
|
|
3643
|
+
|
|
3644
|
+
// view: viewRoot
|
|
3645
|
+
|
|
3646
|
+
layout(std140) uniform Mark {
|
|
3647
|
+
/** Minimum size (width, height) of the displayed rectangle in pixels */
|
|
3648
|
+
uniform float uMinWidth;
|
|
3649
|
+
uniform float uMinHeight;
|
|
3650
|
+
|
|
3651
|
+
/** Minimum opacity for the size clamping */
|
|
3652
|
+
uniform float uMinOpacity;
|
|
3653
|
+
|
|
3654
|
+
uniform float uCornerRadiusTopRight;
|
|
3655
|
+
uniform float uCornerRadiusBottomRight;
|
|
3656
|
+
uniform float uCornerRadiusTopLeft;
|
|
3657
|
+
uniform float uCornerRadiusBottomLeft;
|
|
3658
|
+
|
|
3659
|
+
uniform int uHatchPattern;
|
|
3660
|
+
|
|
3661
|
+
uniform vec3 uShadowColor;
|
|
3662
|
+
uniform float uShadowOpacity;
|
|
3663
|
+
uniform float uShadowBlur;
|
|
3664
|
+
uniform float uShadowOffsetX;
|
|
3665
|
+
uniform float uShadowOffsetY;
|
|
3666
|
+
|
|
3667
|
+
// Selection parameter
|
|
3668
|
+
uniform highp uint uParam_highlight;
|
|
3669
|
+
mediump float uDomain_x[2];
|
|
3670
|
+
|
|
3671
|
+
mediump float uDomain_y[2];
|
|
3672
|
+
|
|
3673
|
+
|
|
3674
|
+
|
|
3675
|
+
uniform highp float attr_y2;
|
|
3676
|
+
|
|
3677
|
+
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
|
|
3681
|
+
#define STROKED
|
|
3682
|
+
|
|
3683
|
+
#define PI 3.141593
|
|
3684
|
+
|
|
3685
|
+
uniform View {
|
|
3686
|
+
/** Offset in "unit" units */
|
|
3687
|
+
mediump vec2 uViewOffset;
|
|
3688
|
+
mediump vec2 uViewScale;
|
|
3689
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
3690
|
+
mediump vec2 uViewportSize;
|
|
3691
|
+
lowp float uDevicePixelRatio;
|
|
3692
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
3693
|
+
// that is rendered with the specified opacity.
|
|
3694
|
+
lowp float uViewOpacity;
|
|
3695
|
+
bool uPickingEnabled;
|
|
3696
|
+
};
|
|
3697
|
+
|
|
3698
|
+
|
|
3699
|
+
/**
|
|
3700
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
3701
|
+
* (0, 0) is at the bottom left corner.
|
|
3702
|
+
*/
|
|
3703
|
+
vec4 unitToNdc(vec2 coord) {
|
|
3704
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
vec4 unitToNdc(float x, float y) {
|
|
3708
|
+
return unitToNdc(vec2(x, y));
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
3712
|
+
return unitToNdc(coord / uViewportSize);
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
3716
|
+
return pixelsToNdc(vec2(x, y));
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
3720
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
const highp uint HASH_EMPTY_KEY = 0xffffffffu;
|
|
3724
|
+
|
|
3725
|
+
highp uint hash32(highp uint key) {
|
|
3726
|
+
highp uint v = key;
|
|
3727
|
+
v ^= v >> 16u;
|
|
3728
|
+
v *= 0x7feb352du;
|
|
3729
|
+
v ^= v >> 15u;
|
|
3730
|
+
v *= 0x846ca68bu;
|
|
3731
|
+
v ^= v >> 16u;
|
|
3732
|
+
return v;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
bool isEmptyHashTexture(highp usampler2D s) {
|
|
3736
|
+
// Empty selections are encoded as a single empty hash slot.
|
|
3737
|
+
ivec2 texSize = textureSize(s, 0);
|
|
3738
|
+
return texSize.x == 1 && texSize.y == 1 && texelFetch(s, ivec2(0, 0), 0).r == HASH_EMPTY_KEY;
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
bool hashContainsTexture(highp usampler2D s, highp uint value) {
|
|
3742
|
+
ivec2 texSize = textureSize(s, 0);
|
|
3743
|
+
highp uint width = uint(texSize.x);
|
|
3744
|
+
highp uint size = width * uint(texSize.y);
|
|
3745
|
+
highp uint mask = size - 1u;
|
|
3746
|
+
highp uint index = hash32(value) & mask;
|
|
3747
|
+
|
|
3748
|
+
for (highp uint probe = 0u; probe < size; probe += 1u) {
|
|
3749
|
+
ivec2 coord = ivec2(int(index % width), int(index / width));
|
|
3750
|
+
highp uint entry = texelFetch(s, coord, 0).r;
|
|
3751
|
+
if (entry == value) {
|
|
3752
|
+
return true;
|
|
3753
|
+
}
|
|
3754
|
+
if (entry == HASH_EMPTY_KEY) {
|
|
3755
|
+
return false;
|
|
3756
|
+
}
|
|
3757
|
+
index = (index + 1u) & mask;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
return false;
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3763
|
+
/**
|
|
3764
|
+
* Calculates a gamma for antialiasing opacity based on the color.
|
|
3765
|
+
*/
|
|
3766
|
+
float getGammaForColor(vec3 rgb) {
|
|
3767
|
+
return mix(
|
|
3768
|
+
1.25,
|
|
3769
|
+
0.75,
|
|
3770
|
+
// RGB should be linearized but this is good enough for now
|
|
3771
|
+
smoothstep(0.0, 1.0, dot(rgb, vec3(0.299, 0.587, 0.114))));
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
3775
|
+
|
|
3776
|
+
// TODO: include the following only in fragment shaders
|
|
3777
|
+
|
|
3778
|
+
/**
|
|
3779
|
+
* Specialized linearstep for doing antialiasing
|
|
3780
|
+
*/
|
|
3781
|
+
float distanceToRatio(float d) {
|
|
3782
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, vec4 background, float halfStrokeWidth) {
|
|
3786
|
+
if (halfStrokeWidth > 0.0) {
|
|
3787
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
3788
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
3789
|
+
return mix(
|
|
3790
|
+
stroke,
|
|
3791
|
+
d <= 0.0 ? fill : background,
|
|
3792
|
+
distanceToRatio(sd));
|
|
3793
|
+
} else {
|
|
3794
|
+
return mix(background, fill, distanceToRatio(-d));
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
|
|
3799
|
+
uniform highp float uZero;
|
|
3800
|
+
|
|
3801
|
+
// Utils ------------
|
|
3802
|
+
|
|
3803
|
+
vec3 getDiscreteColor(sampler2D s, int index) {
|
|
3804
|
+
return texelFetch(s, ivec2(index % textureSize(s, 0).x, 0), 0).rgb;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
vec3 getInterpolatedColor(sampler2D s, float unitValue) {
|
|
3808
|
+
return texture(s, vec2(unitValue, 0.0)).rgb;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
float clampToRange(float value, vec2 range) {
|
|
3812
|
+
return clamp(value, min(range[0], range[1]), max(range[0], range[1]));
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
// Scales ------------
|
|
3816
|
+
// Based on d3 scales: https://github.com/d3/d3-scale
|
|
3817
|
+
|
|
3818
|
+
float scaleIdentity(float value) {
|
|
3819
|
+
return value;
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
float scaleIdentity(uint value) {
|
|
3823
|
+
return float(value);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
float scaleLinear(float value, vec2 domain, vec2 range) {
|
|
3827
|
+
float domainSpan = domain[1] - domain[0];
|
|
3828
|
+
float rangeSpan = range[1] - range[0];
|
|
3829
|
+
return (value - domain[0]) / domainSpan * rangeSpan + range[0];
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
float scaleLog(float value, vec2 domain, vec2 range, float base) {
|
|
3833
|
+
// y = m log(x) + b
|
|
3834
|
+
// TODO: Perf optimization: precalculate log domain in js.
|
|
3835
|
+
// TODO: Reversed domain, etc
|
|
3836
|
+
return scaleLinear(log(value) / log(base), log(domain) / log(base), range);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
float symlog(float value, float constant) {
|
|
3840
|
+
// WARNING: emulating log1p with log(x + 1). Small numbers are likely to
|
|
3841
|
+
// have significant precision problems.
|
|
3842
|
+
return sign(value) * log(abs(value / constant) + 1.0);
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
float scaleSymlog(float value, vec2 domain, vec2 range, float constant) {
|
|
3846
|
+
return scaleLinear(
|
|
3847
|
+
symlog(value, constant),
|
|
3848
|
+
vec2(symlog(domain[0], constant), symlog(domain[1], constant)),
|
|
3849
|
+
range
|
|
3850
|
+
);
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
float scalePow(float value, vec2 domain, vec2 range, float exponent) {
|
|
3854
|
+
// y = mx^k + b
|
|
3855
|
+
// TODO: Perf optimization: precalculate pow domain in js.
|
|
3856
|
+
// TODO: Reversed domain, etc
|
|
3857
|
+
return scaleLinear(
|
|
3858
|
+
pow(abs(value), exponent) * sign(value),
|
|
3859
|
+
pow(abs(domain), vec2(exponent)) * sign(domain),
|
|
3860
|
+
range
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
// TODO: scaleThreshold
|
|
3865
|
+
// TODO: scaleQuantile (special case of threshold scale)
|
|
3866
|
+
|
|
3867
|
+
// TODO: domainExtent should be uint
|
|
3868
|
+
float scaleBand(uint value, vec2 domainExtent, vec2 range,
|
|
3869
|
+
float paddingInner, float paddingOuter,
|
|
3870
|
+
float align, float band) {
|
|
3871
|
+
|
|
3872
|
+
// TODO: reverse
|
|
3873
|
+
float start = range[0];
|
|
3874
|
+
float stop = range[1];
|
|
3875
|
+
float rangeSpan = stop - start;
|
|
3876
|
+
|
|
3877
|
+
float n = domainExtent[1] - domainExtent[0];
|
|
3878
|
+
|
|
3879
|
+
// This fix departs from Vega and d3: https://github.com/vega/vega/issues/3357#issuecomment-1063253596
|
|
3880
|
+
paddingInner = int(n) > 1 ? paddingInner : 0.0;
|
|
3881
|
+
|
|
3882
|
+
// Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
|
|
3883
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
3884
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
3885
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
3886
|
+
|
|
3887
|
+
return start + (float(value) - domainExtent[0]) * step + bandwidth * band;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
const int lowBits = 12;
|
|
3891
|
+
const float lowDivisor = pow(2.0, float(lowBits));
|
|
3892
|
+
const uint lowMask = uint(lowDivisor - 1.0);
|
|
3893
|
+
|
|
3894
|
+
vec2 splitUint(uint value) {
|
|
3895
|
+
uint valueLo = value & lowMask;
|
|
3896
|
+
uint valueHi = value - valueLo;
|
|
3897
|
+
return vec2(float(valueHi), float(valueLo));
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
/**
|
|
3901
|
+
* High precision variant of scaleBand for index/locus scales
|
|
3902
|
+
*/
|
|
3903
|
+
float scaleBandHp(uint value, vec3 domainExtent, vec2 range,
|
|
3904
|
+
float paddingInner, float paddingOuter,
|
|
3905
|
+
float align, float band) {
|
|
3906
|
+
|
|
3907
|
+
// TODO: reverse
|
|
3908
|
+
float start = range[0];
|
|
3909
|
+
float stop = range[1];
|
|
3910
|
+
float rangeSpan = stop - start;
|
|
3911
|
+
|
|
3912
|
+
vec2 domainStart = domainExtent.xy;
|
|
3913
|
+
float n = domainExtent[2];
|
|
3914
|
+
|
|
3915
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
3916
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
3917
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
3918
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
3919
|
+
|
|
3920
|
+
// Split into to values with each having a reduced number of significant digits
|
|
3921
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
3922
|
+
vec2 splitValue = splitUint(value);
|
|
3923
|
+
|
|
3924
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
3925
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
3926
|
+
// some equivalent form that does premature rounding.
|
|
3927
|
+
float inf = 1.0 / uZero;
|
|
3928
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
3929
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
3930
|
+
|
|
3931
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
/**
|
|
3935
|
+
* High precision variant of scaleBand for index/locus scales for large
|
|
3936
|
+
* domains where 32bit uints are not sufficient to represent the domain.
|
|
3937
|
+
*/
|
|
3938
|
+
float scaleBandHp(uvec2 value, vec3 domainExtent, vec2 range,
|
|
3939
|
+
float paddingInner, float paddingOuter,
|
|
3940
|
+
float align, float band) {
|
|
3941
|
+
|
|
3942
|
+
// TODO: reverse
|
|
3943
|
+
float start = range[0];
|
|
3944
|
+
float stop = range[1];
|
|
3945
|
+
float rangeSpan = stop - start;
|
|
3946
|
+
|
|
3947
|
+
vec2 domainStart = domainExtent.xy;
|
|
3948
|
+
float n = domainExtent[2];
|
|
3949
|
+
|
|
3950
|
+
// The following computation is identical for every vertex. Could be done on the JS side.
|
|
3951
|
+
float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
|
|
3952
|
+
start += (rangeSpan - step * (n - paddingInner)) * align;
|
|
3953
|
+
float bandwidth = step * (1.0 - paddingInner);
|
|
3954
|
+
|
|
3955
|
+
// Split into to values with each having a reduced number of significant digits
|
|
3956
|
+
// to mitigate the lack of precision in float32 calculations.
|
|
3957
|
+
vec2 splitValue = vec2(float(value[0]) * lowDivisor, float(value[1]));
|
|
3958
|
+
|
|
3959
|
+
// Using max to prevent the shader compiler from wrecking the precision.
|
|
3960
|
+
// Othwewise the compiler could optimize the sum of the four terms into
|
|
3961
|
+
// some equivalent form that does premature rounding.
|
|
3962
|
+
float inf = 1.0 / uZero;
|
|
3963
|
+
float hi = max(splitValue[0] - domainStart[0], -inf);
|
|
3964
|
+
float lo = max(splitValue[1] - domainStart[1], -inf);
|
|
3965
|
+
|
|
3966
|
+
return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
|
|
3970
|
+
in highp uint attr_uniqueId;
|
|
3971
|
+
in highp uint attr_x;
|
|
3972
|
+
in highp float attr_y;
|
|
3973
|
+
in highp uint attr_x2;
|
|
3974
|
+
|
|
3975
|
+
// Selection texture
|
|
3976
|
+
uniform highp usampler2D uSelectionTexture_select;
|
|
3977
|
+
|
|
3978
|
+
bool checkSelection_select(bool empty) {
|
|
3979
|
+
return hashContainsTexture(uSelectionTexture_select, attr_uniqueId) || (empty && isEmptyHashTexture(uSelectionTexture_select));
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
bool checkSelection_highlight(bool empty) {
|
|
3983
|
+
return uParam_highlight == attr_uniqueId || (empty && uParam_highlight == 0u);
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
|
|
3987
|
+
uint accessor_uniqueId_0() {
|
|
3988
|
+
return attr_uniqueId;
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
uint getScaled_uniqueId() {
|
|
3992
|
+
return accessor_uniqueId_0();
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
#define uniqueId_DEFINED
|
|
3996
|
+
|
|
3997
|
+
|
|
3998
|
+
vec3 accessor_fill_0() {
|
|
3999
|
+
// Constant value
|
|
4000
|
+
return vec3(0.2980392156862745, 0.47058823529411764, 0.6588235294117647);
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
vec3 getScaled_fill() {
|
|
4004
|
+
return accessor_fill_0();
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
#define fill_DEFINED
|
|
4008
|
+
|
|
4009
|
+
|
|
4010
|
+
vec3 accessor_stroke_0() {
|
|
4011
|
+
// Constant value
|
|
4012
|
+
return vec3(0.0, 0.0, 0.0);
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
vec3 getScaled_stroke() {
|
|
4016
|
+
return accessor_stroke_0();
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
#define stroke_DEFINED
|
|
4020
|
+
|
|
4021
|
+
|
|
4022
|
+
float accessor_strokeWidth_0() {
|
|
4023
|
+
// Constant value
|
|
4024
|
+
return float(2.0);
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
|
|
4028
|
+
float accessor_strokeWidth_1() {
|
|
4029
|
+
// Constant value
|
|
4030
|
+
return float(1.0);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
|
|
4034
|
+
float accessor_strokeWidth_2() {
|
|
4035
|
+
// Constant value
|
|
4036
|
+
return float(0.0);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
float getScaled_strokeWidth() {
|
|
4040
|
+
if (checkSelection_select(false)) {
|
|
4041
|
+
return accessor_strokeWidth_0();
|
|
4042
|
+
}
|
|
4043
|
+
else if (checkSelection_highlight(false)) {
|
|
4044
|
+
return accessor_strokeWidth_1();
|
|
4045
|
+
}
|
|
4046
|
+
else {
|
|
4047
|
+
return accessor_strokeWidth_2();
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
#define strokeWidth_DEFINED
|
|
4052
|
+
|
|
4053
|
+
|
|
4054
|
+
uint accessor_x_0() {
|
|
4055
|
+
return attr_x;
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
|
|
4059
|
+
//////////////////////////////////////////////////////////////////////
|
|
4060
|
+
// Channel: x
|
|
4061
|
+
|
|
4062
|
+
const vec2 range_x = vec2(0.0, 1.0);
|
|
4063
|
+
|
|
4064
|
+
float scale_x(uint value) {
|
|
4065
|
+
int slot = 0;
|
|
4066
|
+
vec2 domain = vec2(uDomain_x[slot], uDomain_x[slot + 1]);
|
|
4067
|
+
float transformed = scaleBand(value, domain, range_x, 0.2, 0.2, 0.5, 0.0);
|
|
4068
|
+
return transformed;
|
|
4069
|
+
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
float getScaled_x() {
|
|
4073
|
+
return scale_x(accessor_x_0());
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
#define x_DEFINED
|
|
4077
|
+
|
|
4078
|
+
|
|
4079
|
+
float accessor_y_0() {
|
|
4080
|
+
return attr_y;
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
|
|
4084
|
+
//////////////////////////////////////////////////////////////////////
|
|
4085
|
+
// Channel: y
|
|
4086
|
+
|
|
4087
|
+
const vec2 range_y = vec2(0.0, 1.0);
|
|
4088
|
+
|
|
4089
|
+
float scale_y(float value) {
|
|
4090
|
+
int slot = 0;
|
|
4091
|
+
vec2 domain = vec2(uDomain_y[slot], uDomain_y[slot + 1]);
|
|
4092
|
+
float transformed = scaleLinear(value, domain, range_y);
|
|
4093
|
+
return transformed;
|
|
4094
|
+
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
float getScaled_y() {
|
|
4098
|
+
return scale_y(accessor_y_0());
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
#define y_DEFINED
|
|
4102
|
+
|
|
4103
|
+
|
|
4104
|
+
float accessor_fillOpacity_0() {
|
|
4105
|
+
// Constant value
|
|
4106
|
+
return float(1.0);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
|
|
4110
|
+
float accessor_fillOpacity_1() {
|
|
4111
|
+
// Constant value
|
|
4112
|
+
return float(0.3);
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
float getScaled_fillOpacity() {
|
|
4116
|
+
if (checkSelection_select(true)) {
|
|
4117
|
+
return accessor_fillOpacity_0();
|
|
4118
|
+
}
|
|
4119
|
+
else {
|
|
4120
|
+
return accessor_fillOpacity_1();
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
#define fillOpacity_DEFINED
|
|
4125
|
+
|
|
4126
|
+
|
|
4127
|
+
uint accessor_x2_0() {
|
|
4128
|
+
return attr_x2;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
|
|
4132
|
+
//////////////////////////////////////////////////////////////////////
|
|
4133
|
+
// Channel: x2
|
|
4134
|
+
|
|
4135
|
+
|
|
4136
|
+
float scale_x2(uint value) {
|
|
4137
|
+
int slot = 0;
|
|
4138
|
+
vec2 domain = vec2(uDomain_x[slot], uDomain_x[slot + 1]);
|
|
4139
|
+
float transformed = scaleBand(value, domain, range_x, 0.2, 0.2, 0.5, 1.0);
|
|
4140
|
+
return transformed;
|
|
4141
|
+
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
float getScaled_x2() {
|
|
4145
|
+
return scale_x2(accessor_x2_0());
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
#define x2_DEFINED
|
|
4149
|
+
|
|
4150
|
+
|
|
4151
|
+
float accessor_y2_0() {
|
|
4152
|
+
return attr_y2;
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
|
|
4156
|
+
//////////////////////////////////////////////////////////////////////
|
|
4157
|
+
// Channel: y2
|
|
4158
|
+
|
|
4159
|
+
|
|
4160
|
+
float scale_y2(float value) {
|
|
4161
|
+
int slot = 0;
|
|
4162
|
+
vec2 domain = vec2(uDomain_y[slot], uDomain_y[slot + 1]);
|
|
4163
|
+
float transformed = scaleLinear(value, domain, range_y);
|
|
4164
|
+
return transformed;
|
|
4165
|
+
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
float getScaled_y2() {
|
|
4169
|
+
return scale_y2(accessor_y2_0());
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
#define y2_DEFINED
|
|
4173
|
+
|
|
4174
|
+
|
|
4175
|
+
float accessor_strokeOpacity_0() {
|
|
4176
|
+
// Constant value
|
|
4177
|
+
return float(1.0);
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
float getScaled_strokeOpacity() {
|
|
4181
|
+
return accessor_strokeOpacity_0();
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
#define strokeOpacity_DEFINED
|
|
4185
|
+
|
|
4186
|
+
bool isPointSelected() {
|
|
4187
|
+
return checkSelection_select(false) || checkSelection_highlight(false);
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
|
|
4191
|
+
/**
|
|
4192
|
+
* Describes where a sample facet should be shown. Interpolating between the
|
|
4193
|
+
* current and target positions/heights allows for transitioning between facet
|
|
4194
|
+
* configurations.
|
|
4195
|
+
*/
|
|
4196
|
+
struct SampleFacetPosition {
|
|
4197
|
+
float pos;
|
|
4198
|
+
float height;
|
|
4199
|
+
float targetPos;
|
|
4200
|
+
float targetHeight;
|
|
4201
|
+
};
|
|
4202
|
+
|
|
4203
|
+
/**
|
|
4204
|
+
* Trasition fraction [0, 1] between the current and target configurations.
|
|
4205
|
+
*/
|
|
4206
|
+
uniform float uTransitionOffset;
|
|
4207
|
+
|
|
4208
|
+
|
|
4209
|
+
// ----------------------------------------------------------------------------
|
|
4210
|
+
|
|
4211
|
+
#if !defined(SAMPLE_FACET_UNIFORM) && !defined(SAMPLE_FACET_TEXTURE)
|
|
4212
|
+
|
|
4213
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
4214
|
+
return SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
#elif defined(SAMPLE_FACET_UNIFORM)
|
|
4218
|
+
|
|
4219
|
+
/**
|
|
4220
|
+
* Location and height of the band on the Y axis on a normalized [0, 1] scale.
|
|
4221
|
+
* Elements: curr pos, curr height, target pos, target height
|
|
4222
|
+
*/
|
|
4223
|
+
uniform vec4 uSampleFacet;
|
|
4224
|
+
|
|
4225
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
4226
|
+
return SampleFacetPosition(
|
|
4227
|
+
1.0 - uSampleFacet.x - uSampleFacet.y,
|
|
4228
|
+
uSampleFacet.y,
|
|
4229
|
+
1.0 - uSampleFacet.z - uSampleFacet.w,
|
|
4230
|
+
uSampleFacet.w
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
#elif defined(SAMPLE_FACET_TEXTURE)
|
|
4235
|
+
|
|
4236
|
+
uniform sampler2D uSampleFacetTexture;
|
|
4237
|
+
|
|
4238
|
+
SampleFacetPosition getSampleFacetPos() {
|
|
4239
|
+
vec4 texel = texelFetch(uSampleFacetTexture, ivec2(int(attr_facetIndex), 0), 0);
|
|
4240
|
+
return SampleFacetPosition(
|
|
4241
|
+
1.0 - texel.r - texel.g,
|
|
4242
|
+
texel.g,
|
|
4243
|
+
1.0 - texel.r - texel.g,
|
|
4244
|
+
texel.g);
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
#endif
|
|
4248
|
+
|
|
4249
|
+
// ----------------------------------------------------------------------------
|
|
4250
|
+
|
|
4251
|
+
bool isFacetedSamples(SampleFacetPosition facetPos) {
|
|
4252
|
+
return facetPos != SampleFacetPosition(0.0, 1.0, 0.0, 1.0);
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
bool isFacetedSamples() {
|
|
4256
|
+
return isFacetedSamples(getSampleFacetPos());
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
bool isInTransit() {
|
|
4260
|
+
return uTransitionOffset > 0.0;
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
float getTransitionFraction(float xPos) {
|
|
4264
|
+
return smoothstep(0.0, 0.7 + uTransitionOffset, (xPos - uTransitionOffset) * 2.0);
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4267
|
+
vec2 applySampleFacet(vec2 pos) {
|
|
4268
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
4269
|
+
|
|
4270
|
+
if (!isFacetedSamples(facetPos)) {
|
|
4271
|
+
return pos;
|
|
4272
|
+
} else if (isInTransit()) {
|
|
4273
|
+
vec2 interpolated = mix(
|
|
4274
|
+
vec2(facetPos.pos, facetPos.height),
|
|
4275
|
+
vec2(facetPos.targetPos, facetPos.targetHeight),
|
|
4276
|
+
getTransitionFraction(pos.x));
|
|
4277
|
+
return vec2(pos.x, interpolated[0] + pos.y * interpolated[1]);
|
|
4278
|
+
} else {
|
|
4279
|
+
return vec2(pos.x, facetPos.pos + pos.y * facetPos.height);
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
float getSampleFacetHeight(vec2 pos) {
|
|
4284
|
+
SampleFacetPosition facetPos = getSampleFacetPos();
|
|
4285
|
+
|
|
4286
|
+
if (!isFacetedSamples(facetPos)) {
|
|
4287
|
+
return 1.0;
|
|
4288
|
+
} else if (isInTransit()) {
|
|
4289
|
+
return mix(
|
|
4290
|
+
facetPos.height,
|
|
4291
|
+
facetPos.targetHeight,
|
|
4292
|
+
getTransitionFraction(pos.x));
|
|
4293
|
+
} else {
|
|
4294
|
+
return facetPos.height;
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
|
|
4299
|
+
/*
|
|
4300
|
+
* Based on concepts presented at:
|
|
4301
|
+
* https://webglfundamentals.org/webgl/lessons/webgl-picking.html
|
|
4302
|
+
* https://deck.gl/docs/developer-guide/custom-layers/picking
|
|
4303
|
+
*/
|
|
4304
|
+
|
|
4305
|
+
out highp vec4 vPickingColor;
|
|
4306
|
+
|
|
4307
|
+
/**
|
|
4308
|
+
* Passes the unique id to the fragment shader as a color if picking is enabled.
|
|
4309
|
+
* Returns true if picking is enabled.
|
|
4310
|
+
*/
|
|
4311
|
+
bool setupPicking() {
|
|
4312
|
+
if (uPickingEnabled) {
|
|
4313
|
+
#ifdef uniqueId_DEFINED
|
|
4314
|
+
uint id = attr_uniqueId;
|
|
4315
|
+
vPickingColor = vec4(
|
|
4316
|
+
ivec4(id >> 0, id >> 8, id >> 16, id >> 24) & 0xFF
|
|
4317
|
+
) / float(0xFF);
|
|
4318
|
+
#else
|
|
4319
|
+
vPickingColor = vec4(1.0);
|
|
4320
|
+
#endif
|
|
4321
|
+
return true;
|
|
4322
|
+
}
|
|
4323
|
+
return false;
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
|
|
4327
|
+
flat out lowp vec4 vFillColor;
|
|
4328
|
+
flat out lowp vec4 vStrokeColor;
|
|
4329
|
+
flat out float vHalfStrokeWidth;
|
|
4330
|
+
flat out vec4 vCornerRadii;
|
|
4331
|
+
|
|
4332
|
+
#if defined(ROUNDED_CORNERS) || defined(STROKED) || defined(SHADOW)
|
|
4333
|
+
/** Position for SDF-strokes */
|
|
4334
|
+
out vec2 vPosInPixels;
|
|
4335
|
+
#endif
|
|
4336
|
+
|
|
4337
|
+
/** Size of the rect in pixels */
|
|
4338
|
+
flat out vec2 vHalfSizeInPixels;
|
|
4339
|
+
|
|
4340
|
+
/**
|
|
4341
|
+
* Adjusts the vertex position to ensure that the rectangle is at least \`minSpan\`
|
|
4342
|
+
* wide or high. Returns a value that reflects the amount of clamping and can be
|
|
4343
|
+
* used to adjust the opacity of the rectangle.
|
|
4344
|
+
*
|
|
4345
|
+
* pos: vertex position
|
|
4346
|
+
* frac: vertex position within the rectangle, [0, 1]
|
|
4347
|
+
* size: width or height of the rectangle
|
|
4348
|
+
* minSize: minimum width or height of the rectangle
|
|
4349
|
+
*/
|
|
4350
|
+
float clampMinSize(inout float pos, float frac, float size, float minSize) {
|
|
4351
|
+
if (minSize > 0.0 && size < minSize) {
|
|
4352
|
+
pos += (frac - 0.5) * (minSize - size);
|
|
4353
|
+
return size / minSize;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
return 1.0;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
void sort(inout float a, inout float b) {
|
|
4360
|
+
if (a > b) {
|
|
4361
|
+
float tmp = b;
|
|
4362
|
+
b = a;
|
|
4363
|
+
a = tmp;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
/**
|
|
4368
|
+
* The vertex position wrt the rectangle specified by (x, x2, y, y2).
|
|
4369
|
+
* [0, 0] = [x, y], [1, 1] = [x2, y2].
|
|
4370
|
+
* The x or y component may contain fractional values if the rectangle
|
|
4371
|
+
* have been tessellated.
|
|
4372
|
+
*/
|
|
4373
|
+
vec2 getVertexPos() {
|
|
4374
|
+
int index = gl_VertexID % 6;
|
|
4375
|
+
return vec2(
|
|
4376
|
+
index == 0 || index == 1 || index == 3 ? 0.0 : 1.0,
|
|
4377
|
+
index == 0 || index == 1 || index == 2 ? 0.0 : 1.0
|
|
4378
|
+
);
|
|
4379
|
+
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
void main(void) {
|
|
4383
|
+
vec2 frac = getVertexPos();
|
|
4384
|
+
|
|
4385
|
+
vec2 normalizedMinSize = vec2(uMinWidth, uMinHeight) / uViewportSize;
|
|
4386
|
+
vec4 cornerRadii = vec4(
|
|
4387
|
+
uCornerRadiusTopRight,
|
|
4388
|
+
uCornerRadiusBottomRight,
|
|
4389
|
+
uCornerRadiusTopLeft,
|
|
4390
|
+
uCornerRadiusBottomLeft
|
|
4391
|
+
);
|
|
4392
|
+
|
|
4393
|
+
float x = getScaled_x();
|
|
4394
|
+
float x2 = getScaled_x2();
|
|
4395
|
+
float y = getScaled_y();
|
|
4396
|
+
float y2 = getScaled_y2();
|
|
4397
|
+
|
|
4398
|
+
sort(x, x2);
|
|
4399
|
+
sort(y, y2);
|
|
4400
|
+
|
|
4401
|
+
// Clamp x to prevent precision artifacts when the scale is zoomed very close.
|
|
4402
|
+
// TODO: clamp y as well
|
|
4403
|
+
float clampMargin = 1.0;
|
|
4404
|
+
vec2 pos1 = vec2(clamp(x, 0.0 - clampMargin, 1.0 + clampMargin), y);
|
|
4405
|
+
vec2 pos2 = vec2(clamp(x2, 0.0 - clampMargin, 1.0 + clampMargin), y2);
|
|
4406
|
+
|
|
4407
|
+
vec2 size = pos2 - pos1;
|
|
4408
|
+
|
|
4409
|
+
if (size.x <= 0.0 || size.y <= 0.0) {
|
|
4410
|
+
// Early exit. May increase performance or not...
|
|
4411
|
+
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
vec2 pos = pos1 + frac * size;
|
|
4416
|
+
|
|
4417
|
+
size.y *= getSampleFacetHeight(pos);
|
|
4418
|
+
|
|
4419
|
+
// Clamp to minimum size, optionally compensate with opacity
|
|
4420
|
+
float opaFactor = uViewOpacity * max(uMinOpacity,
|
|
4421
|
+
clampMinSize(pos.x, frac.x, size.x, normalizedMinSize.x) *
|
|
4422
|
+
clampMinSize(pos.y, frac.y, size.y, normalizedMinSize.y));
|
|
4423
|
+
|
|
4424
|
+
size = max(size, normalizedMinSize);
|
|
4425
|
+
|
|
4426
|
+
pos = applySampleFacet(pos);
|
|
4427
|
+
|
|
4428
|
+
#if defined(ROUNDED_CORNERS) || defined(STROKED) || defined(SHADOW)
|
|
4429
|
+
// Add an extra pixel to the stroke width to accommodate edge antialiasing
|
|
4430
|
+
float aaPadding = 1.0 / uDevicePixelRatio;
|
|
4431
|
+
|
|
4432
|
+
// TODO: Only expand to the offset direction. Now high offsets result in
|
|
4433
|
+
// a large expansion in all directions.
|
|
4434
|
+
float shadowPadding = uShadowBlur + max(abs(uShadowOffsetX), abs(uShadowOffsetY));
|
|
4435
|
+
|
|
4436
|
+
float strokeWidth = getScaled_strokeWidth();
|
|
4437
|
+
float strokeOpacity = getScaled_strokeOpacity() * opaFactor;
|
|
4438
|
+
|
|
4439
|
+
vec2 centeredFrac = frac - 0.5;
|
|
4440
|
+
vec2 expand = centeredFrac * (strokeWidth + aaPadding + shadowPadding * 2.0) / uViewportSize;
|
|
4441
|
+
pos += expand;
|
|
4442
|
+
|
|
4443
|
+
vec2 sizeInPixels = size * uViewportSize;
|
|
4444
|
+
vPosInPixels = (centeredFrac + expand / size) * sizeInPixels;
|
|
4445
|
+
|
|
4446
|
+
vHalfSizeInPixels = sizeInPixels / 2.0;
|
|
4447
|
+
|
|
4448
|
+
vCornerRadii = min(cornerRadii, min(vHalfSizeInPixels.x, vHalfSizeInPixels.y));
|
|
4449
|
+
vHalfStrokeWidth = strokeWidth / 2.0;
|
|
4450
|
+
vStrokeColor = vec4(getScaled_stroke() * strokeOpacity, strokeOpacity);
|
|
4451
|
+
#endif
|
|
4452
|
+
|
|
4453
|
+
gl_Position = unitToNdc(pos);
|
|
4454
|
+
|
|
4455
|
+
float fillOpacity = getScaled_fillOpacity() * opaFactor;
|
|
4456
|
+
vFillColor = vec4(getScaled_fill() * fillOpacity, fillOpacity);
|
|
4457
|
+
|
|
4458
|
+
setupPicking();
|
|
4459
|
+
}
|
|
4460
|
+
",
|
|
4461
|
+
}
|
|
4462
|
+
`;
|