diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte b/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte new file mode 100644 index 0000000..d30a38a --- /dev/null +++ b/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte @@ -0,0 +1,111 @@ + + + + {@render children?.({ ref: material })} + diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag b/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag new file mode 100644 index 0000000..4163333 --- /dev/null +++ b/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag @@ -0,0 +1,30 @@ +uniform vec3 colorStart; +uniform vec3 colorEnd; + +uniform float useDash; +uniform float dashArray; +uniform float dashOffset; +uniform float dashRatio; +uniform sampler2D alphaMap; +uniform float useAlphaMap; + +varying vec2 vUV; +varying vec4 vColor; +varying float vCounters; + +vec4 CustomLinearTosRGB( in vec4 value ) { + return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a ); +} + +void main() { + + vec4 c = mix(vec4(colorStart,1.0),vec4(colorEnd, 1.0), vCounters); + + if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV ).r; + + if( useDash == 1. ){ + c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio)); + } + + gl_FragColor = CustomLinearTosRGB(c); +} diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts b/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts new file mode 100644 index 0000000..15e07da --- /dev/null +++ b/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts @@ -0,0 +1,68 @@ +import type { Props } from '@threlte/core'; +import type { BufferGeometry, Vector3 } from 'three'; +import type { ColorRepresentation, ShaderMaterial, Texture } from 'three'; + +export type MeshLineGeometryProps = Props & { + /** + * @default [] + */ + points: Vector3[]; + + /** + * @default 'none' + */ + shape?: 'none' | 'taper' | 'custom'; + + /** + * @default () => 1 + */ + shapeFunction?: (p: number) => number; +}; + +export type MeshLineMaterialProps = + & Omit< + Props, + 'uniforms' | 'fragmentShader' | 'vertexShader' + > + & { + /** + * @default 1 + */ + opacity?: number; + + /** + * @default '#ffffff' + */ + color?: ColorRepresentation; + + /** + * @default 0 + */ + dashOffset?: number; + + /** + * @default 0 + */ + dashArray?: number; + + /** + * @default 0 + */ + dashRatio?: number; + + /** + * @default true + */ + attenuate?: boolean; + + /** + * @default 1 + */ + width?: number; + + /** + * @default 0 + */ + scaleDown?: number; + alphaMap?: Texture | undefined; + }; diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert b/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert new file mode 100644 index 0000000..039a59f --- /dev/null +++ b/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert @@ -0,0 +1,83 @@ +attribute vec3 previous; +attribute vec3 next; +attribute float side; +attribute float width; +attribute float counters; + +uniform vec2 resolution; +uniform float lineWidth; +uniform vec3 color; +uniform float opacity; +uniform float sizeAttenuation; +uniform float scaleDown; + +varying vec2 vUV; +varying vec4 vColor; +varying float vCounters; + +vec2 intoScreen(vec4 i) { + return resolution * (0.5 * i.xy / i.w + 0.5); +} + +void main() { + float aspect = resolution.y / resolution.x; + + mat4 m = projectionMatrix * modelViewMatrix; + + vec4 currentClip = m * vec4( position, 1.0 ); + vec4 prevClip = m * vec4( previous, 1.0 ); + vec4 nextClip = m * vec4( next, 1.0 ); + + vec4 currentNormed = currentClip / currentClip.w; + vec4 prevNormed = prevClip / prevClip.w; + vec4 nextNormed = nextClip / nextClip.w; + + vec2 currentScreen = intoScreen(currentNormed); + vec2 prevScreen = intoScreen(prevNormed); + vec2 nextScreen = intoScreen(nextNormed); + + float actualWidth = lineWidth * width; + + vec2 dir; + if(nextScreen == currentScreen) { + dir = normalize( currentScreen - prevScreen ); + } else if(prevScreen == currentScreen) { + dir = normalize( nextScreen - currentScreen ); + } else { + vec2 inDir = currentScreen - prevScreen; + vec2 outDir = nextScreen - currentScreen; + vec2 fullDir = nextScreen - prevScreen; + + if(length(fullDir) > 0.0) { + dir = normalize(fullDir); + } else if(length(inDir) > 0.0){ + dir = normalize(inDir); + } else { + dir = normalize(outDir); + } + } + + vec2 normal = vec2(-dir.y, dir.x); + + if(sizeAttenuation != 0.0) { + normal /= currentClip.w; + normal *= min(resolution.x, resolution.y); + } + + if (scaleDown > 0.0) { + float dist = length(nextNormed - prevNormed); + normal *= smoothstep(0.0, scaleDown, dist); + } + + vec2 offsetInScreen = actualWidth * normal * side * 0.5; + + vec2 withOffsetScreen = currentScreen + offsetInScreen; + vec3 withOffsetNormed = vec3((2.0 * withOffsetScreen/resolution - 1.0), currentNormed.z); + + vCounters = counters; + vColor = vec4( color, opacity ); + vUV = uv; + + gl_Position = currentClip.w * vec4(withOffsetNormed, 1.0); + +}