Files
vue-bits/public/r/Beams.json
David Haz e621971723 jsrepo v3
2025-12-15 23:50:24 +02:00

1 line
14 KiB
JSON

{"name":"Beams","title":"Beams","description":"Crossing animated ribbons with customizable properties.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div ref=\"containerRef\" class=\"beams-container w-full h-full relative\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, computed, useTemplateRef } from 'vue';\nimport * as THREE from 'three';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst props = withDefaults(defineProps<BeamsProps>(), {\n beamWidth: 2,\n beamHeight: 15,\n beamNumber: 12,\n lightColor: '#ffffff',\n speed: 2,\n noiseIntensity: 1.75,\n scale: 0.2,\n rotation: 0\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n\nlet renderer: THREE.WebGLRenderer | null = null;\nlet scene: THREE.Scene | null = null;\nlet camera: THREE.PerspectiveCamera | null = null;\nlet beamMesh: THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial> | null = null;\nlet directionalLight: THREE.DirectionalLight | null = null;\nlet ambientLight: THREE.AmbientLight | null = null;\nlet animationId: number | null = null;\n\ntype UniformValue = THREE.IUniform<unknown> | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record<string, UniformValue>;\n vertex?: Record<string, string>;\n fragment?: Record<string, string>;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record<string, string | number | boolean>;\n};\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\nfunction extendMaterial<T extends THREE.Material = THREE.Material>(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record<string, THREE.IUniform> = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform<unknown>)\n : ({ value: u } as THREE.IUniform<unknown>);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst beamMaterial = computed(() =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include <begin_vertex>': `transformed.z += getPos(transformed.xyz);`,\n '#include <beginnormal_vertex>': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include <dithering_fragment>': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: props.speed },\n envMapIntensity: 10,\n uNoiseIntensity: props.noiseIntensity,\n uScale: props.scale\n }\n })\n);\n\nconst initThreeJS = () => {\n if (!containerRef.value) return;\n\n cleanup();\n\n const container = containerRef.value;\n\n renderer = new THREE.WebGLRenderer({ antialias: true });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 1);\n\n scene = new THREE.Scene();\n\n camera = new THREE.PerspectiveCamera(30, 1, 0.1, 1000);\n camera.position.set(0, 0, 20);\n\n const geometry = createStackedPlanesBufferGeometry(props.beamNumber, props.beamWidth, props.beamHeight, 0, 100);\n\n const material = beamMaterial.value;\n beamMesh = new THREE.Mesh(geometry, material);\n\n const group = new THREE.Group();\n group.rotation.z = degToRad(props.rotation);\n group.add(beamMesh);\n scene.add(group);\n\n directionalLight = new THREE.DirectionalLight(new THREE.Color(props.lightColor), 1);\n directionalLight.position.set(0, 3, 10);\n const shadowCamera = directionalLight.shadow.camera as THREE.OrthographicCamera;\n shadowCamera.top = 24;\n shadowCamera.bottom = -24;\n shadowCamera.left = -24;\n shadowCamera.right = 24;\n shadowCamera.far = 64;\n directionalLight.shadow.bias = -0.004;\n scene.add(directionalLight);\n\n ambientLight = new THREE.AmbientLight(0xffffff, 1);\n scene.add(ambientLight);\n\n container.appendChild(renderer.domElement);\n\n const resize = () => {\n if (!container || !renderer || !camera) return;\n\n const width = container.offsetWidth;\n const height = container.offsetHeight;\n\n renderer.setSize(width, height);\n camera.aspect = width / height;\n camera.updateProjectionMatrix();\n };\n\n const resizeObserver = new ResizeObserver(resize);\n resizeObserver.observe(container);\n\n resize();\n\n const animate = () => {\n animationId = requestAnimationFrame(animate);\n\n if (beamMesh && beamMesh.material) {\n beamMesh.material.uniforms.time.value += 0.1 * 0.016;\n }\n\n if (renderer && scene && camera) {\n renderer.render(scene, camera);\n }\n };\n\n animationId = requestAnimationFrame(animate);\n (container as HTMLDivElement & { _resizeObserver?: ResizeObserver })._resizeObserver = resizeObserver;\n};\n\nconst cleanup = () => {\n if (animationId) {\n cancelAnimationFrame(animationId);\n animationId = null;\n }\n\n if (containerRef.value) {\n const container = containerRef.value as HTMLDivElement & { _resizeObserver?: ResizeObserver };\n\n if (container._resizeObserver) {\n container._resizeObserver.disconnect();\n delete container._resizeObserver;\n }\n\n if (renderer && renderer.domElement.parentNode === container) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (beamMesh) {\n if (beamMesh.geometry) beamMesh.geometry.dispose();\n if (beamMesh.material) beamMesh.material.dispose();\n beamMesh = null;\n }\n\n if (renderer) {\n renderer.dispose();\n renderer = null;\n }\n\n scene = null;\n camera = null;\n directionalLight = null;\n ambientLight = null;\n};\n\nwatch(\n () => [\n props.beamWidth,\n props.beamHeight,\n props.beamNumber,\n props.lightColor,\n props.speed,\n props.noiseIntensity,\n props.scale,\n props.rotation\n ],\n () => {\n initThreeJS();\n },\n { deep: true }\n);\n\nonMounted(() => {\n initThreeJS();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n</script>\n","path":"Beams/Beams.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Backgrounds"]}