From d9d3648baaff12463cbb90dcf6b198f23431b622 Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Sun, 12 Apr 2026 16:40:52 +0200 Subject: [PATCH] Halftone studio v3 (glass effect) (#19598) ## Summary - refine `/halftone` controls for material selection and slower 3D rotation tuning - switch the default halftone material to solid for new sessions - include related halftone rendering, export, and glass material updates across the website app ## Testing - `yarn nx run twenty-website-new:typecheck` - `yarn prettier packages/twenty-website-new/src/app/halftone/_components/controls/AnimationsTab.tsx --check` - `yarn prettier packages/twenty-website-new/src/app/halftone/_components/controls/controls-ui.tsx packages/twenty-website-new/src/app/halftone/_components/controls/DesignTab.tsx --check` - `yarn prettier packages/twenty-website-new/src/app/halftone/_lib/state.ts --check` --- .../halftone/materials/glass/environment.jpg | Bin 0 -> 180052 bytes .../halftone/_components/ControlsPanel.tsx | 6 +- .../halftone/_components/HalftoneCanvas.tsx | 2437 +++++++++-------- .../halftone/_components/HalftoneStudio.tsx | 31 +- .../_components/controls/AnimationsTab.tsx | 84 +- .../_components/controls/DesignTab.tsx | 119 +- .../_components/controls/ExportTab.tsx | 10 +- .../_components/controls/controls-ui.tsx | 100 +- .../src/app/halftone/_lib/exportNames.ts | 51 + .../src/app/halftone/_lib/exporters.test.ts | 37 + .../src/app/halftone/_lib/exporters.ts | 853 +++++- .../src/app/halftone/_lib/formatters.ts | 5 + .../app/halftone/_lib/glassEnvironmentData.ts | 2 + .../src/app/halftone/_lib/materials.ts | 880 ++++++ .../src/app/halftone/_lib/state.test.ts | 59 + .../src/app/halftone/_lib/state.ts | 332 ++- .../src/app/partner/_constants/hero.ts | 8 +- .../src/app/product/_constants/hero.ts | 7 +- .../Hero/types/HeroIllustrationData.ts | 5 - .../src/sections/Hero/types/index.ts | 1 - 20 files changed, 3709 insertions(+), 1318 deletions(-) create mode 100644 packages/twenty-website-new/public/halftone/materials/glass/environment.jpg create mode 100644 packages/twenty-website-new/src/app/halftone/_lib/exportNames.ts create mode 100644 packages/twenty-website-new/src/app/halftone/_lib/exporters.test.ts create mode 100644 packages/twenty-website-new/src/app/halftone/_lib/glassEnvironmentData.ts create mode 100644 packages/twenty-website-new/src/app/halftone/_lib/materials.ts create mode 100644 packages/twenty-website-new/src/app/halftone/_lib/state.test.ts delete mode 100644 packages/twenty-website-new/src/sections/Hero/types/HeroIllustrationData.ts diff --git a/packages/twenty-website-new/public/halftone/materials/glass/environment.jpg b/packages/twenty-website-new/public/halftone/materials/glass/environment.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ddd056788a51f943e85510cfb9e40705db8bd43 GIT binary patch literal 180052 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�lLoof8-&-Pwy9(h-AQgTQR}3tjJ-r1o4|!t{XdU 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�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�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 viewAspect) { float scale = viewAspect / imageAspect; uv.y = (uv.y - 0.5) / scale + 0.5; @@ -94,7 +101,7 @@ const imagePassthroughFragmentShader = /* glsl */ ` } `; -const halftoneFragmentShader = /* glsl */ ` +const halftoneFragmentShader = ` precision highp float; uniform sampler2D tScene; @@ -112,6 +119,9 @@ const halftoneFragmentShader = /* glsl */ ` uniform vec2 interactionUv; uniform vec2 interactionVelocity; uniform vec2 dragOffset; + uniform float hoverHalftonePowerShift; + uniform float hoverHalftoneRadius; + uniform float hoverHalftoneWidthShift; uniform float hoverLightStrength; uniform float hoverLightRadius; uniform float hoverFlowStrength; @@ -138,7 +148,6 @@ const halftoneFragmentShader = /* glsl */ ` } void main() { - // Crop to image bounds: discard fragments outside source image (image mode only) if (cropToBounds > 0.5) { vec4 boundsCheck = texture2D(tScene, vUv); if (boundsCheck.a < 0.01) { @@ -168,6 +177,15 @@ const halftoneFragmentShader = /* glsl */ ` hoverLightMask = smoothstep(lightRadiusPx, 0.0, fragDist); } + float hoverHalftoneMask = 0.0; + if ( + abs(hoverHalftonePowerShift) > 0.0001 || + abs(hoverHalftoneWidthShift) > 0.0001 + ) { + float hoverHalftoneRadiusPx = hoverHalftoneRadius * logicalResolution.y; + hoverHalftoneMask = smoothstep(hoverHalftoneRadiusPx, 0.0, fragDist); + } + float hoverFlowMask = 0.0; if (hoverFlowStrength > 0.0) { float hoverRadiusPx = hoverFlowRadius * logicalResolution.y; @@ -195,6 +213,16 @@ const halftoneFragmentShader = /* glsl */ ` vec4 sceneSample = texture2D(tScene, sampleUv); float mask = smoothstep(0.02, 0.08, sceneSample.a); + float localPower = clamp( + s_3 + hoverHalftonePowerShift * hoverHalftoneMask, + -1.5, + 1.5 + ); + float localWidth = clamp( + s_4 + hoverHalftoneWidthShift * hoverHalftoneMask, + 0.05, + 1.4 + ); float lightLift = hoverLightStrength * hoverLightMask * @@ -206,7 +234,7 @@ const halftoneFragmentShader = /* glsl */ ` sceneSample.r + sceneSample.g + sceneSample.b + - s_3 * length(vec2(0.5)) + localPower * length(vec2(0.5)) ) * (1.0 / 3.0) ) + lightLift, @@ -216,7 +244,7 @@ const halftoneFragmentShader = /* glsl */ ` float alpha = 0.0; if (bandRadius > 0.0001) { - float signedDistance = lineSimpleEt(cellUv, bandRadius, s_4); + float signedDistance = lineSimpleEt(cellUv, bandRadius, localWidth); float edge = 0.02; alpha = (1.0 - smoothstep(0.0, edge, signedDistance)) * mask; } @@ -233,17 +261,6 @@ const IMAGE_POINTER_FOLLOW = 0.38; const IMAGE_POINTER_VELOCITY_DAMPING = 0.82; const MAX_PREVIEW_PIXEL_RATIO = 2; -function createEnvironmentTexture(renderer: THREE.WebGLRenderer) { - const pmremGenerator = new THREE.PMREMGenerator(renderer); - const environmentTexture = pmremGenerator.fromScene( - new RoomEnvironment(), - 0.04, - ).texture; - pmremGenerator.dispose(); - - return environmentTexture; -} - const CanvasMount = styled.div<{ $background: string }>` background: ${(props) => props.$background}; display: block; @@ -282,14 +299,14 @@ type SceneResources = { blurVerticalScene: THREE.Scene; camera: THREE.PerspectiveCamera; canvas: HTMLCanvasElement; - environmentTexture: THREE.Texture; fillLight: THREE.DirectionalLight; fullScreenGeometry: THREE.PlaneGeometry; halftoneMaterial: THREE.ShaderMaterial; imageMaterial: THREE.ShaderMaterial; imageScene: THREE.Scene; imageTexture: THREE.Texture | null; - material: THREE.MeshPhysicalMaterial; + materialAssets: HalftoneMaterialAssets; + material: HalftoneTransmissionMaterial; mesh: THREE.Mesh; orthographicCamera: THREE.OrthographicCamera; postScene: THREE.Scene; @@ -297,6 +314,8 @@ type SceneResources = { renderer: THREE.WebGLRenderer; scene3d: THREE.Scene; sceneTarget: THREE.WebGLRenderTarget; + transmissionBacksideTarget: THREE.WebGLRenderTarget; + transmissionTarget: THREE.WebGLRenderTarget; }; type InteractionState = { @@ -455,9 +474,11 @@ function updateMaterial( resources: SceneResources, settings: HalftoneStudioSettings, ) { - resources.material.roughness = settings.material.roughness; - resources.material.metalness = settings.material.metalness; - resources.material.needsUpdate = true; + applyHalftoneMaterialSettings( + resources.material, + settings.material, + resources.materialAssets, + ); } function updateHalftone( @@ -552,6 +573,8 @@ export function HalftoneCanvas({ const initialPoseReference = useRef(initialPose); const poseChangeReference = useRef(onPoseChange); const previewDistanceReference = useRef(previewDistance); + const geometryReference = useRef(geometry); + const snapshotReference = useRef(snapshotRef); useEffect(() => { initialPoseReference.current = initialPose; @@ -591,6 +614,7 @@ export function HalftoneCanvas({ prev.autoRotateEnabled !== next.autoRotateEnabled || prev.followHoverEnabled !== next.followHoverEnabled || prev.followDragEnabled !== next.followDragEnabled || + prev.hoverHalftoneEnabled !== next.hoverHalftoneEnabled || prev.hoverLightEnabled !== next.hoverLightEnabled || prev.dragFlowEnabled !== next.dragFlowEnabled ) { @@ -613,6 +637,8 @@ export function HalftoneCanvas({ }, [settings]); useEffect(() => { + geometryReference.current = geometry; + const resources = resourcesReference.current; if (!resources || !geometry) { @@ -622,6 +648,10 @@ export function HalftoneCanvas({ resources.mesh.geometry = geometry; }, [geometry]); + useEffect(() => { + snapshotReference.current = snapshotRef; + }, [snapshotRef]); + useEffect(() => { const resources = resourcesReference.current; @@ -654,8 +684,11 @@ export function HalftoneCanvas({ useEffect(() => { const container = mountReference.current; + const initialSettings = settingsReference.current; + const initialPreviewDistance = previewDistanceReference.current; + const activeSnapshotRef = snapshotReference.current; - if (!container || !geometry) { + if (!container || !geometryReference.current) { return; } @@ -693,1123 +726,1259 @@ export function HalftoneCanvas({ canvas.style.width = '100%'; container.appendChild(canvas); - const environmentTexture = createEnvironmentTexture(renderer); - - const scene3d = new THREE.Scene(); - scene3d.background = null; - - const camera = new THREE.PerspectiveCamera( - 45, - getWidth() / getHeight(), - 0.1, - 100, - ); - camera.position.z = previewDistance; - - const primaryLight = new THREE.DirectionalLight(0xffffff, 1.5); - scene3d.add(primaryLight); - - const fillLight = new THREE.DirectionalLight(0xffffff, 0.15); - fillLight.position.set(-3, -1, 1); - scene3d.add(fillLight); - - const ambientLight = new THREE.AmbientLight(0xffffff, 0.08); - scene3d.add(ambientLight); - - const material = new THREE.MeshPhysicalMaterial({ - color: 0xd4d0c8, - roughness: 0.42, - metalness: 0.16, - envMap: environmentTexture, - envMapIntensity: 0.25, - clearcoat: 0, - clearcoatRoughness: 0.08, - reflectivity: 0.5, - transmission: 0, - }); - - const mesh = new THREE.Mesh(geometry, material); - scene3d.add(mesh); - - const sceneTarget = createRenderTarget(getRenderWidth(), getRenderHeight()); - const blurTargetA = createRenderTarget(getRenderWidth(), getRenderHeight()); - const blurTargetB = createRenderTarget(getRenderWidth(), getRenderHeight()); - const fullScreenGeometry = new THREE.PlaneGeometry(2, 2); - const orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); - - const blurHorizontalMaterial = new THREE.ShaderMaterial({ - uniforms: { - tInput: { value: null }, - dir: { value: new THREE.Vector2(1, 0) }, - res: { - value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), - }, - }, - vertexShader: passThroughVertexShader, - fragmentShader: blurFragmentShader, - }); - - const blurVerticalMaterial = new THREE.ShaderMaterial({ - uniforms: { - tInput: { value: null }, - dir: { value: new THREE.Vector2(0, 1) }, - res: { - value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), - }, - }, - vertexShader: passThroughVertexShader, - fragmentShader: blurFragmentShader, - }); - - const halftoneMaterial = new THREE.ShaderMaterial({ - transparent: true, - uniforms: { - tScene: { value: sceneTarget.texture }, - tGlow: { value: blurTargetB.texture }, - effectResolution: { - value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), - }, - logicalResolution: { - value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), - }, - tile: { value: settings.halftone.scale }, - s_3: { value: settings.halftone.power }, - s_4: { value: settings.halftone.width }, - dashColor: { value: new THREE.Color(settings.halftone.dashColor) }, - time: { value: 0 }, - waveAmount: { value: 0 }, - waveSpeed: { value: 1 }, - footprintScale: { value: 1.0 }, - interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, - interactionVelocity: { value: new THREE.Vector2(0, 0) }, - dragOffset: { value: new THREE.Vector2(0, 0) }, - hoverLightStrength: { value: 0 }, - hoverLightRadius: { value: 0.2 }, - hoverFlowStrength: { value: 0 }, - hoverFlowRadius: { value: 0.18 }, - dragFlowStrength: { value: 0 }, - cropToBounds: { value: 0 }, - }, - vertexShader: passThroughVertexShader, - fragmentShader: halftoneFragmentShader, - }); - - const blurHorizontalScene = new THREE.Scene(); - blurHorizontalScene.add( - new THREE.Mesh(fullScreenGeometry, blurHorizontalMaterial), - ); - - const blurVerticalScene = new THREE.Scene(); - blurVerticalScene.add( - new THREE.Mesh(fullScreenGeometry, blurVerticalMaterial), - ); - - const postScene = new THREE.Scene(); - postScene.add(new THREE.Mesh(fullScreenGeometry, halftoneMaterial)); - - const imageMaterial = new THREE.ShaderMaterial({ - uniforms: { - tImage: { value: null }, - imageSize: { value: new THREE.Vector2(1, 1) }, - viewportSize: { - value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), - }, - zoom: { value: getImagePreviewZoom(previewDistance) }, - contrast: { value: settings.halftone.imageContrast }, - }, - vertexShader: passThroughVertexShader, - fragmentShader: imagePassthroughFragmentShader, - }); - - const imageScene = new THREE.Scene(); - imageScene.add(new THREE.Mesh(fullScreenGeometry, imageMaterial)); - - const resources: SceneResources = { - ambientLight, - blurHorizontalMaterial, - blurHorizontalScene, - blurTargetA, - blurTargetB, - blurVerticalMaterial, - blurVerticalScene, - camera, - canvas, - environmentTexture, - fillLight, - fullScreenGeometry, - halftoneMaterial, - imageMaterial, - imageScene, - imageTexture: null, - material, - mesh, - orthographicCamera, - postScene, - primaryLight, - renderer, - scene3d, - sceneTarget, - }; - - const updateViewportUniforms = ( - logicalWidth: number, - logicalHeight: number, - effectWidth: number, - effectHeight: number, - ) => { - blurHorizontalMaterial.uniforms.res.value.set(effectWidth, effectHeight); - blurVerticalMaterial.uniforms.res.value.set(effectWidth, effectHeight); - halftoneMaterial.uniforms.effectResolution.value.set( - effectWidth, - effectHeight, - ); - halftoneMaterial.uniforms.logicalResolution.value.set( - logicalWidth, - logicalHeight, - ); - imageMaterial.uniforms.viewportSize.value.set( - logicalWidth, - logicalHeight, - ); - }; - - const getImageHalftoneScale = ( - viewportWidth: number, - viewportHeight: number, - activePreviewDistance: number, - ) => { - const imageSize = imageMaterial.uniforms.imageSize.value as THREE.Vector2; - - return getImageFootprintScale({ - imageHeight: imageSize.y, - imageWidth: imageSize.x, - previewDistance: activePreviewDistance, - viewportHeight, - viewportWidth, - }); - }; - - const getMeshHalftoneScale = ( - viewportWidth: number, - viewportHeight: number, - lookAtTarget: THREE.Vector3, - ) => { - if (!mesh.geometry.boundingBox) { - mesh.geometry.computeBoundingBox(); - } - - const localBounds = mesh.geometry.boundingBox; - - if (!localBounds) { - return 1; - } - - mesh.updateMatrixWorld(); - camera.updateMatrixWorld(); - - return getMeshFootprintScale({ - camera, - localBounds, - lookAtTarget, - meshMatrixWorld: mesh.matrixWorld, - viewportHeight, - viewportWidth, - }); - }; - - resourcesReference.current = resources; - syncResources(resources, settingsReference.current); - - // Snapshot: render the current frame at arbitrary resolution and return a PNG blob - const captureSnapshot: HalftoneSnapshotFn = async ( - snapshotWidth: number, - snapshotHeight: number, - options, - ) => { - const activeSettings = settingsReference.current; - const isImage = - activeSettings.sourceMode === 'image' && - resources.imageTexture !== null; - const includeBackground = options?.includeBackground ?? false; - const backgroundColor = - options?.backgroundColor ?? activeSettings.background.color; - - // Create temporary render targets at export resolution - const snapScene = createRenderTarget(snapshotWidth, snapshotHeight); - const snapBlurA = createRenderTarget(snapshotWidth, snapshotHeight); - const snapBlurB = createRenderTarget(snapshotWidth, snapshotHeight); - - // Save current renderer state - const prevSize = renderer.getSize(new THREE.Vector2()); - - // Resize renderer to snapshot resolution - renderer.setSize(snapshotWidth, snapshotHeight, false); - - // Update material uniforms for snapshot resolution - updateViewportUniforms( - snapshotWidth, - snapshotHeight, - snapshotWidth, - snapshotHeight, - ); - halftoneMaterial.uniforms.hoverLightStrength.value = 0; - halftoneMaterial.uniforms.hoverFlowStrength.value = 0; - halftoneMaterial.uniforms.dragFlowStrength.value = 0; - halftoneMaterial.uniforms.interactionVelocity.value.set(0, 0); - halftoneMaterial.uniforms.dragOffset.value.set(0, 0); - halftoneMaterial.uniforms.cropToBounds.value = isImage ? 1 : 0; - - if (isImage) { - imageMaterial.uniforms.zoom.value = getImagePreviewZoom( - previewDistanceReference.current, - ); - halftoneMaterial.uniforms.footprintScale.value = getImageHalftoneScale( - snapshotWidth, - snapshotHeight, - previewDistanceReference.current, - ); - } else { - camera.aspect = snapshotWidth / snapshotHeight; - camera.updateProjectionMatrix(); - halftoneMaterial.uniforms.footprintScale.value = getMeshHalftoneScale( - snapshotWidth, - snapshotHeight, - new THREE.Vector3(0, mesh.position.y * 0.2, 0), - ); - } - - // Render scene to snapshot target - renderer.setRenderTarget(snapScene); - if (isImage) { - renderer.render(imageScene, orthographicCamera); - } else { - renderer.render(scene3d, camera); - } - - // Blur passes - halftoneMaterial.uniforms.tScene.value = snapScene.texture; - - blurHorizontalMaterial.uniforms.tInput.value = snapScene.texture; - renderer.setRenderTarget(snapBlurA); - renderer.render(blurHorizontalScene, orthographicCamera); - - blurVerticalMaterial.uniforms.tInput.value = snapBlurA.texture; - renderer.setRenderTarget(snapBlurB); - renderer.render(blurVerticalScene, orthographicCamera); - - blurHorizontalMaterial.uniforms.tInput.value = snapBlurB.texture; - renderer.setRenderTarget(snapBlurA); - renderer.render(blurHorizontalScene, orthographicCamera); - - blurVerticalMaterial.uniforms.tInput.value = snapBlurA.texture; - renderer.setRenderTarget(snapBlurB); - renderer.render(blurVerticalScene, orthographicCamera); - - halftoneMaterial.uniforms.tGlow.value = snapBlurB.texture; - - // Final halftone render to a target we can read pixels from - const outputTarget = createRenderTarget(snapshotWidth, snapshotHeight); - renderer.setRenderTarget(outputTarget); - renderer.clear(); - renderer.render(postScene, orthographicCamera); - - // Read pixels - const pixelBuffer = new Uint8Array(snapshotWidth * snapshotHeight * 4); - renderer.readRenderTargetPixels( - outputTarget, - 0, - 0, - snapshotWidth, - snapshotHeight, - pixelBuffer, - ); - - // Restore renderer state - renderer.setSize(prevSize.x, prevSize.y, false); - halftoneMaterial.uniforms.tScene.value = sceneTarget.texture; - halftoneMaterial.uniforms.tGlow.value = blurTargetB.texture; - updateViewportUniforms( - getVirtualWidth(), - getVirtualHeight(), - prevSize.x, - prevSize.y, - ); - if (isImage) { - imageMaterial.uniforms.zoom.value = getImagePreviewZoom( - previewDistanceReference.current, - ); - } else { - camera.aspect = getWidth() / Math.max(getHeight(), 1); - camera.updateProjectionMatrix(); - } - - // Dispose temporary targets - snapScene.dispose(); - snapBlurA.dispose(); - snapBlurB.dispose(); - outputTarget.dispose(); - - // Convert pixels to PNG via canvas - // WebGL readPixels returns rows bottom-to-top, so flip vertically - const flippedBuffer = new Uint8Array(snapshotWidth * snapshotHeight * 4); - const rowSize = snapshotWidth * 4; - for (let y = 0; y < snapshotHeight; y++) { - const srcOffset = y * rowSize; - const dstOffset = (snapshotHeight - 1 - y) * rowSize; - flippedBuffer.set( - pixelBuffer.subarray(srcOffset, srcOffset + rowSize), - dstOffset, - ); - } - - const fullSnapshotBounds = { - minX: 0, - minY: 0, - maxX: snapshotWidth - 1, - maxY: snapshotHeight - 1, - }; - const alphaCropBounds = getAlphaCropBounds( - flippedBuffer, - snapshotWidth, - snapshotHeight, - ); - const cropBounds = - includeBackground - ? fullSnapshotBounds - : (alphaCropBounds ?? fullSnapshotBounds); - const croppedWidth = cropBounds.maxX - cropBounds.minX + 1; - const croppedHeight = cropBounds.maxY - cropBounds.minY + 1; - const croppedBuffer = new Uint8ClampedArray( - croppedWidth * croppedHeight * 4, - ); - - for (let y = 0; y < croppedHeight; y++) { - const sourceStart = - ((cropBounds.minY + y) * snapshotWidth + cropBounds.minX) * 4; - const sourceEnd = sourceStart + croppedWidth * 4; - const destinationStart = y * croppedWidth * 4; - - croppedBuffer.set( - flippedBuffer.subarray(sourceStart, sourceEnd), - destinationStart, - ); - } - - const imageData = new ImageData( - croppedBuffer, - croppedWidth, - croppedHeight, - ); - const offscreen = document.createElement('canvas'); - offscreen.width = croppedWidth; - offscreen.height = croppedHeight; - const ctx = offscreen.getContext('2d'); - - if (!ctx) { - return null; - } - - if (includeBackground) { - const sourceCanvas = document.createElement('canvas'); - sourceCanvas.width = croppedWidth; - sourceCanvas.height = croppedHeight; - const sourceContext = sourceCanvas.getContext('2d'); - - if (!sourceContext) { - return null; - } - - sourceContext.putImageData(imageData, 0, 0); - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, croppedWidth, croppedHeight); - ctx.drawImage(sourceCanvas, 0, 0); - } else { - ctx.putImageData(imageData, 0, 0); - } - - return new Promise((resolve) => { - offscreen.toBlob((blob) => resolve(blob), 'image/png'); - }); - }; - - if (snapshotRef) { - snapshotRef.current = captureSnapshot; - } - - const syncSize = () => { - if (cancelled) { - return; - } - - const width = getWidth(); - const height = getHeight(); - const logicalWidth = getVirtualWidth(); - const logicalHeight = getVirtualHeight(); - const renderWidth = getRenderWidth(); - const renderHeight = getRenderHeight(); - - renderer.setSize(renderWidth, renderHeight, false); - camera.aspect = width / height; - camera.updateProjectionMatrix(); - sceneTarget.setSize(renderWidth, renderHeight); - blurTargetA.setSize(renderWidth, renderHeight); - blurTargetB.setSize(renderWidth, renderHeight); - updateViewportUniforms( - logicalWidth, - logicalHeight, - renderWidth, - renderHeight, - ); - }; - - const resizeObserver = new ResizeObserver(syncSize); - resizeObserver.observe(container); - - const updatePointerPosition = ( - event: PointerEvent, - options?: { resetVelocity?: boolean }, - ) => { - const interaction = interactionReference.current; - const rect = canvas.getBoundingClientRect(); - const width = Math.max(rect.width, 1); - const height = Math.max(rect.height, 1); - - const nextMouseX = THREE.MathUtils.clamp( - (event.clientX - rect.left) / width, - 0, - 1, - ); - const nextMouseY = THREE.MathUtils.clamp( - (event.clientY - rect.top) / height, - 0, - 1, - ); - - const deltaX = nextMouseX - interaction.mouseX; - const deltaY = nextMouseY - interaction.mouseY; - - interaction.mouseX = nextMouseX; - interaction.mouseY = nextMouseY; - interaction.pointerInside = - interaction.dragging || - (event.clientX >= rect.left && - event.clientX <= rect.right && - event.clientY >= rect.top && - event.clientY <= rect.bottom); - - if (options?.resetVelocity) { - interaction.pointerVelocityX = 0; - interaction.pointerVelocityY = 0; - interaction.smoothedMouseX = nextMouseX; - interaction.smoothedMouseY = nextMouseY; - } else { - interaction.pointerVelocityX = deltaX; - interaction.pointerVelocityY = deltaY; - } - - return { deltaX, deltaY }; - }; - - const releasePointerCapture = (pointerId: number | null) => { - if (pointerId === null) { - return; - } - - if (!canvas.hasPointerCapture(pointerId)) { - return; - } - - try { - canvas.releasePointerCapture(pointerId); - } catch { - // Ignore capture release failures during teardown. - } - }; - - const markFirstInteraction = () => { - if (didInteractReference.current) { - return; - } - - didInteractReference.current = true; - onFirstInteraction(); - }; - - const handlePointerDown = (event: PointerEvent) => { - const interaction = interactionReference.current; - const activeSettings = settingsReference.current; - const canDrag = - activeSettings.sourceMode === 'image' - ? false - : activeSettings.animation.followDragEnabled; - - updatePointerPosition(event, { resetVelocity: true }); - interaction.pointerX = event.clientX; - interaction.pointerY = event.clientY; - - if (canDrag) { - interaction.dragging = true; - interaction.activePointerId = event.pointerId; - interaction.velocityX = 0; - interaction.velocityY = 0; - - try { - canvas.setPointerCapture(event.pointerId); - } catch { - // Pointer capture can fail in some browsers if the canvas is detached. - } - } - - canvas.style.cursor = getCanvasCursor( - activeSettings, - interaction.dragging, - ); - - markFirstInteraction(); - }; - - const handlePointerMove = (event: PointerEvent) => { - const interaction = interactionReference.current; - const resetVelocity = !interaction.pointerInside && !interaction.dragging; - updatePointerPosition( - event, - resetVelocity ? { resetVelocity: true } : undefined, - ); - const activeSettings = settingsReference.current; - - if (activeSettings.sourceMode === 'image' && interaction.pointerInside) { - markFirstInteraction(); - } - - if (!interaction.dragging) { - return; - } - - if ( - interaction.activePointerId !== null && - event.pointerId !== interaction.activePointerId - ) { - return; - } - - const animation = activeSettings.animation; - - if (!animation.followDragEnabled) { - return; - } - - const deltaX = - (event.clientX - interaction.pointerX) * animation.dragSens; - const deltaY = - (event.clientY - interaction.pointerY) * animation.dragSens; - interaction.velocityX = deltaY; - interaction.velocityY = deltaX; - interaction.targetRotationY += deltaX; - interaction.targetRotationX += deltaY; - interaction.pointerX = event.clientX; - interaction.pointerY = event.clientY; - }; - - const handlePointerLeave = () => { - const interaction = interactionReference.current; - const activeSettings = settingsReference.current; - - if (interaction.dragging) { - return; - } - - interaction.pointerInside = false; - interaction.pointerVelocityX = 0; - interaction.pointerVelocityY = 0; - - if (activeSettings.sourceMode !== 'image') { - interaction.mouseX = 0.5; - interaction.mouseY = 0.5; - } - - canvas.style.cursor = getCanvasCursor(activeSettings, false); - }; - - const handlePointerUp = (event: PointerEvent) => { - const interaction = interactionReference.current; - const activeSettings = settingsReference.current; - const animation = activeSettings.animation; - - updatePointerPosition(event, { resetVelocity: true }); - releasePointerCapture(interaction.activePointerId); - interaction.activePointerId = null; - interaction.dragging = false; - const rect = canvas.getBoundingClientRect(); - interaction.pointerInside = - event.clientX >= rect.left && - event.clientX <= rect.right && - event.clientY >= rect.top && - event.clientY <= rect.bottom; - - if (!interaction.pointerInside && activeSettings.sourceMode !== 'image') { - interaction.mouseX = 0.5; - interaction.mouseY = 0.5; - } - - canvas.style.cursor = getCanvasCursor(activeSettings, false); - - if (!animation.springReturnEnabled) { - return; - } - - const springImpulse = Math.max(animation.springStrength * 10, 1.2); - interaction.rotationVelocityX += interaction.velocityX * springImpulse; - interaction.rotationVelocityY += interaction.velocityY * springImpulse; - interaction.rotationVelocityZ += - interaction.velocityY * springImpulse * 0.12; - interaction.targetRotationX = 0; - interaction.targetRotationY = 0; - interaction.velocityX = 0; - interaction.velocityY = 0; - }; - - const handlePointerCancel = () => { - const interaction = interactionReference.current; - const activeSettings = settingsReference.current; - releasePointerCapture(interaction.activePointerId); - interaction.activePointerId = null; - interaction.dragging = false; - interaction.pointerInside = false; - interaction.pointerVelocityX = 0; - interaction.pointerVelocityY = 0; - - interaction.mouseX = 0.5; - interaction.mouseY = 0.5; - interaction.smoothedMouseX = 0.5; - interaction.smoothedMouseY = 0.5; - - canvas.style.cursor = getCanvasCursor(activeSettings, false); - }; - - const handleWindowBlur = () => { - handlePointerCancel(); - }; - - canvas.addEventListener('pointermove', handlePointerMove); - canvas.addEventListener('pointerleave', handlePointerLeave); - canvas.addEventListener('pointerup', handlePointerUp); - canvas.addEventListener('pointercancel', handlePointerCancel); - window.addEventListener('blur', handleWindowBlur); - canvas.addEventListener('pointerdown', handlePointerDown); - - const clock = new THREE.Clock(); - - const renderFrame = () => { - if (cancelled) { - return; - } - - animationFrameId = window.requestAnimationFrame(renderFrame); - - const interaction = interactionReference.current; - const activeSettings = settingsReference.current; - const delta = clock.getDelta(); - const elapsedTime = - (initialPoseReference.current?.timeElapsed ?? 0) + - clock.elapsedTime; - const baseDistance = previewDistanceReference.current; - const logicalWidth = getVirtualWidth(); - const logicalHeight = getVirtualHeight(); - const isImageMode = activeSettings.sourceMode === 'image'; - const hasImageTexture = resources.imageTexture !== null; - - halftoneMaterial.uniforms.time.value = elapsedTime; - halftoneMaterial.uniforms.waveAmount.value = - activeSettings.animation.waveEnabled && !isImageMode - ? activeSettings.animation.waveAmount - : 0; - halftoneMaterial.uniforms.waveSpeed.value = - activeSettings.animation.waveSpeed; - - // Image mode selected but no image loaded yet — show empty canvas - if (isImageMode && !hasImageTexture) { - renderer.setRenderTarget(null); - renderer.clear(); - return; - } - - halftoneMaterial.uniforms.cropToBounds.value = isImageMode ? 1 : 0; - - if (isImageMode) { - const pointerActive = interaction.pointerInside; - - interaction.smoothedMouseX += - (interaction.mouseX - interaction.smoothedMouseX) * - IMAGE_POINTER_FOLLOW; - interaction.smoothedMouseY += - (interaction.mouseY - interaction.smoothedMouseY) * - IMAGE_POINTER_FOLLOW; - interaction.pointerVelocityX *= IMAGE_POINTER_VELOCITY_DAMPING; - interaction.pointerVelocityY *= IMAGE_POINTER_VELOCITY_DAMPING; - - halftoneMaterial.uniforms.interactionUv.value.set( - interaction.smoothedMouseX, - 1 - interaction.smoothedMouseY, - ); - halftoneMaterial.uniforms.interactionVelocity.value.set( - interaction.pointerVelocityX * logicalWidth, - -interaction.pointerVelocityY * logicalHeight, - ); - halftoneMaterial.uniforms.dragOffset.value.set(0, 0); - halftoneMaterial.uniforms.hoverLightStrength.value = - pointerActive && activeSettings.animation.hoverLightEnabled - ? activeSettings.animation.hoverLightIntensity - : 0; - halftoneMaterial.uniforms.hoverLightRadius.value = - activeSettings.animation.hoverLightRadius; - halftoneMaterial.uniforms.hoverFlowStrength.value = 0; - halftoneMaterial.uniforms.hoverFlowRadius.value = 0.18; - halftoneMaterial.uniforms.dragFlowStrength.value = 0; - - imageMaterial.uniforms.zoom.value = getImagePreviewZoom(baseDistance); - imageMaterial.uniforms.viewportSize.value.set( - logicalWidth, - logicalHeight, - ); - halftoneMaterial.uniforms.footprintScale.value = getImageHalftoneScale( - logicalWidth, - logicalHeight, - baseDistance, - ); - - poseChangeReference.current({ - autoElapsed: 0, - rotateElapsed: 0, - rotationX: 0, - rotationY: 0, - rotationZ: 0, - targetRotationX: interaction.targetRotationX, - targetRotationY: interaction.targetRotationY, - timeElapsed: elapsedTime, - }); - - if (!activeSettings.halftone.enabled) { - renderer.setRenderTarget(null); - renderer.clear(); - renderer.render(imageScene, orthographicCamera); - return; - } - - renderer.setRenderTarget(sceneTarget); - renderer.render(imageScene, orthographicCamera); - } - - if (!isImageMode) { - let baseRotationX = 0; - let baseRotationY = 0; - let baseRotationZ = 0; - let meshOffsetY = 0; - let meshScale = 1; - let lightAngle = activeSettings.lighting.angleDegrees; - let lightHeight = activeSettings.lighting.height; - - if (activeSettings.animation.autoRotateEnabled) { - interaction.autoElapsed += delta; - baseRotationY += - interaction.autoElapsed * activeSettings.animation.autoSpeed; - baseRotationX += - Math.sin(interaction.autoElapsed * 0.2) * - activeSettings.animation.autoWobble; - } - - if (activeSettings.animation.floatEnabled) { - const floatPhase = elapsedTime * activeSettings.animation.floatSpeed; - const driftAmount = - (activeSettings.animation.driftAmount * Math.PI) / 180; - - meshOffsetY += - Math.sin(floatPhase) * activeSettings.animation.floatAmplitude; - baseRotationX += Math.sin(floatPhase * 0.72) * driftAmount * 0.45; - baseRotationZ += Math.cos(floatPhase * 0.93) * driftAmount * 0.3; - } - - if (activeSettings.animation.breatheEnabled) { - meshScale *= - 1 + - Math.sin(elapsedTime * activeSettings.animation.breatheSpeed) * - activeSettings.animation.breatheAmount; - } - - if (activeSettings.animation.rotateEnabled) { - interaction.rotateElapsed += delta; - const rotateProgress = activeSettings.animation.rotatePingPong - ? Math.sin( - interaction.rotateElapsed * - activeSettings.animation.rotateSpeed, - ) * Math.PI - : interaction.rotateElapsed * activeSettings.animation.rotateSpeed; - - if (activeSettings.animation.rotatePreset === 'axis') { - const axisDirection = - activeSettings.animation.rotateAxis.startsWith('-') ? -1 : 1; - const axisProgress = rotateProgress * axisDirection; - - if ( - activeSettings.animation.rotateAxis === 'x' || - activeSettings.animation.rotateAxis === 'xy' || - activeSettings.animation.rotateAxis === '-x' || - activeSettings.animation.rotateAxis === '-xy' - ) { - baseRotationX += axisProgress; - } - - if ( - activeSettings.animation.rotateAxis === 'y' || - activeSettings.animation.rotateAxis === 'xy' || - activeSettings.animation.rotateAxis === '-y' || - activeSettings.animation.rotateAxis === '-xy' - ) { - baseRotationY += axisProgress; - } - - if ( - activeSettings.animation.rotateAxis === 'z' || - activeSettings.animation.rotateAxis === '-z' - ) { - baseRotationZ += axisProgress; - } - } else if (activeSettings.animation.rotatePreset === 'lissajous') { - baseRotationX += Math.sin(rotateProgress * 0.85) * 0.65; - baseRotationY += Math.sin(rotateProgress * 1.35 + 0.8) * 1.05; - baseRotationZ += Math.sin(rotateProgress * 0.55 + 1.6) * 0.32; - } else if (activeSettings.animation.rotatePreset === 'orbit') { - baseRotationX += Math.sin(rotateProgress * 0.75) * 0.42; - baseRotationY += Math.cos(rotateProgress) * 1.2; - baseRotationZ += Math.sin(rotateProgress * 1.25) * 0.24; - } else if (activeSettings.animation.rotatePreset === 'tumble') { - baseRotationX += rotateProgress * 0.55; - baseRotationY += Math.sin(rotateProgress * 0.8) * 0.9; - baseRotationZ += Math.cos(rotateProgress * 1.1) * 0.38; - } - } - - if (activeSettings.animation.lightSweepEnabled) { - const lightPhase = - elapsedTime * activeSettings.animation.lightSweepSpeed; - lightAngle += - Math.sin(lightPhase) * activeSettings.animation.lightSweepRange; - lightHeight += - Math.cos(lightPhase * 0.85) * - activeSettings.animation.lightSweepHeightRange; - } - - let targetX = baseRotationX; - let targetY = baseRotationY; - let easing = 0.12; - - if (activeSettings.animation.followHoverEnabled) { - const rangeRadians = - (activeSettings.animation.hoverRange * Math.PI) / 180; - - if ( - activeSettings.animation.hoverReturn || - interaction.mouseX !== 0.5 || - interaction.mouseY !== 0.5 - ) { - targetX += (interaction.mouseY - 0.5) * rangeRadians; - targetY += (interaction.mouseX - 0.5) * rangeRadians; - } - - easing = activeSettings.animation.hoverEase; - } - - if (activeSettings.animation.followDragEnabled) { - if (!interaction.dragging && activeSettings.animation.dragMomentum) { - interaction.targetRotationX += interaction.velocityX; - interaction.targetRotationY += interaction.velocityY; - interaction.velocityX *= 1 - activeSettings.animation.dragFriction; - interaction.velocityY *= 1 - activeSettings.animation.dragFriction; - } - - targetX += interaction.targetRotationX; - targetY += interaction.targetRotationY; - easing = activeSettings.animation.dragFriction; - } - - if ( - activeSettings.animation.autoRotateEnabled && - !activeSettings.animation.followHoverEnabled && - !activeSettings.animation.followDragEnabled - ) { - targetX = baseRotationX + interaction.targetRotationX; - targetY = baseRotationY + interaction.targetRotationY; - - if (interaction.dragging) { - targetX = interaction.targetRotationX; - targetY = interaction.targetRotationY; - } - - easing = 0.08; - } - - if (activeSettings.animation.springReturnEnabled) { - const springX = applySpringStep( - interaction.rotationX, - targetX, - interaction.rotationVelocityX, - activeSettings.animation.springStrength, - activeSettings.animation.springDamping, - ); - const springY = applySpringStep( - interaction.rotationY, - targetY, - interaction.rotationVelocityY, - activeSettings.animation.springStrength, - activeSettings.animation.springDamping, - ); - const springZ = applySpringStep( - interaction.rotationZ, - baseRotationZ, - interaction.rotationVelocityZ, - activeSettings.animation.springStrength, - activeSettings.animation.springDamping, - ); - - interaction.rotationX = springX.value; - interaction.rotationY = springY.value; - interaction.rotationZ = springZ.value; - interaction.rotationVelocityX = springX.velocity; - interaction.rotationVelocityY = springY.velocity; - interaction.rotationVelocityZ = springZ.velocity; - } else { - interaction.rotationX += (targetX - interaction.rotationX) * easing; - interaction.rotationY += (targetY - interaction.rotationY) * easing; - interaction.rotationZ += - (baseRotationZ - interaction.rotationZ) * - (activeSettings.animation.rotatePingPong ? 0.18 : 0.12); - } - - mesh.rotation.set( - interaction.rotationX, - interaction.rotationY, - interaction.rotationZ, - ); - mesh.position.y = meshOffsetY; - mesh.scale.setScalar(meshScale); - - if (activeSettings.animation.cameraParallaxEnabled) { - const cameraRange = activeSettings.animation.cameraParallaxAmount; - const cameraEase = activeSettings.animation.cameraParallaxEase; - const centeredX = (interaction.mouseX - 0.5) * 2; - const centeredY = (0.5 - interaction.mouseY) * 2; - const orbitYaw = centeredX * cameraRange; - const orbitPitch = centeredY * cameraRange * 0.7; - const horizontalRadius = Math.cos(orbitPitch) * baseDistance; - const targetCameraX = Math.sin(orbitYaw) * horizontalRadius; - const targetCameraY = Math.sin(orbitPitch) * baseDistance * 0.85; - const targetCameraZ = Math.cos(orbitYaw) * horizontalRadius; - - camera.position.x += (targetCameraX - camera.position.x) * cameraEase; - camera.position.y += (targetCameraY - camera.position.y) * cameraEase; - camera.position.z += (targetCameraZ - camera.position.z) * cameraEase; - } else { - camera.position.x += (0 - camera.position.x) * 0.12; - camera.position.y += (0 - camera.position.y) * 0.12; - camera.position.z += (baseDistance - camera.position.z) * 0.12; - } - - const lookAtTarget = new THREE.Vector3(0, meshOffsetY * 0.2, 0); - - camera.lookAt(lookAtTarget); - setPrimaryLightPosition(primaryLight, lightAngle, lightHeight); - halftoneMaterial.uniforms.footprintScale.value = getMeshHalftoneScale( - logicalWidth, - logicalHeight, - lookAtTarget, - ); - - poseChangeReference.current({ - autoElapsed: interaction.autoElapsed, - rotateElapsed: interaction.rotateElapsed, - rotationX: interaction.rotationX, - rotationY: interaction.rotationY, - rotationZ: interaction.rotationZ, - targetRotationX: interaction.targetRotationX, - targetRotationY: interaction.targetRotationY, - timeElapsed: elapsedTime, - }); - - if (!activeSettings.halftone.enabled) { - renderer.setRenderTarget(null); - renderer.clear(); - renderer.render(scene3d, camera); - return; - } - - renderer.setRenderTarget(sceneTarget); - renderer.render(scene3d, camera); - halftoneMaterial.uniforms.hoverLightStrength.value = 0; - halftoneMaterial.uniforms.hoverFlowStrength.value = 0; - halftoneMaterial.uniforms.dragFlowStrength.value = 0; - halftoneMaterial.uniforms.interactionVelocity.value.set(0, 0); - halftoneMaterial.uniforms.dragOffset.value.set(0, 0); - } // end if (!isImageMode) - - blurHorizontalMaterial.uniforms.tInput.value = sceneTarget.texture; - renderer.setRenderTarget(blurTargetA); - renderer.render(blurHorizontalScene, orthographicCamera); - - blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; - renderer.setRenderTarget(blurTargetB); - renderer.render(blurVerticalScene, orthographicCamera); - - blurHorizontalMaterial.uniforms.tInput.value = blurTargetB.texture; - renderer.setRenderTarget(blurTargetA); - renderer.render(blurHorizontalScene, orthographicCamera); - - blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; - renderer.setRenderTarget(blurTargetB); - renderer.render(blurVerticalScene, orthographicCamera); - - renderer.setRenderTarget(null); - renderer.clear(); - renderer.render(postScene, orthographicCamera); - }; - - renderFrame(); - - return () => { - cancelled = true; - resizeObserver.disconnect(); - canvas.removeEventListener('pointermove', handlePointerMove); - canvas.removeEventListener('pointerleave', handlePointerLeave); - canvas.removeEventListener('pointerup', handlePointerUp); - canvas.removeEventListener('pointercancel', handlePointerCancel); - window.removeEventListener('blur', handleWindowBlur); - canvas.removeEventListener('pointerdown', handlePointerDown); - window.cancelAnimationFrame(animationFrameId); - - blurHorizontalMaterial.dispose(); - blurVerticalMaterial.dispose(); - halftoneMaterial.dispose(); - imageMaterial.dispose(); - - if (resources.imageTexture) { - resources.imageTexture.dispose(); - } - - fullScreenGeometry.dispose(); - material.dispose(); - sceneTarget.dispose(); - blurTargetA.dispose(); - blurTargetB.dispose(); - environmentTexture.dispose(); + let cleanup = () => { renderer.dispose(); resourcesReference.current = null; - if (snapshotRef) { - snapshotRef.current = null; + if (activeSnapshotRef) { + activeSnapshotRef.current = null; } if (canvas.parentNode === container) { container.removeChild(canvas); } }; + + void (async () => { + const materialAssets = await createHalftoneMaterialAssets(renderer); + + if (cancelled) { + disposeHalftoneMaterialAssets(materialAssets); + cleanup(); + + return; + } + + const scene3d = new THREE.Scene(); + scene3d.background = null; + + const camera = new THREE.PerspectiveCamera( + 45, + getWidth() / getHeight(), + 0.1, + 100, + ); + camera.position.z = initialPreviewDistance; + + const primaryLight = new THREE.DirectionalLight(0xffffff, 1.5); + scene3d.add(primaryLight); + + const fillLight = new THREE.DirectionalLight(0xffffff, 0.15); + fillLight.position.set(-3, -1, 1); + scene3d.add(fillLight); + + const ambientLight = new THREE.AmbientLight(0xffffff, 0.08); + scene3d.add(ambientLight); + + const material = createHalftoneMaterial(); + applyHalftoneMaterialSettings( + material, + settingsReference.current.material, + materialAssets, + ); + + const currentGeometry = geometryReference.current; + + if (!currentGeometry) { + disposeHalftoneMaterialAssets(materialAssets); + cleanup(); + + return; + } + + const mesh = new THREE.Mesh(currentGeometry, material); + scene3d.add(mesh); + + const sceneTarget = createRenderTarget( + getRenderWidth(), + getRenderHeight(), + ); + const transmissionBacksideTarget = createRenderTarget( + getRenderWidth(), + getRenderHeight(), + ); + const transmissionTarget = createRenderTarget( + getRenderWidth(), + getRenderHeight(), + ); + const blurTargetA = createRenderTarget( + getRenderWidth(), + getRenderHeight(), + ); + const blurTargetB = createRenderTarget( + getRenderWidth(), + getRenderHeight(), + ); + const fullScreenGeometry = new THREE.PlaneGeometry(2, 2); + const orthographicCamera = new THREE.OrthographicCamera( + -1, + 1, + 1, + -1, + 0, + 1, + ); + + const blurHorizontalMaterial = new THREE.ShaderMaterial({ + uniforms: { + tInput: { value: null }, + dir: { value: new THREE.Vector2(1, 0) }, + res: { + value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), + }, + }, + vertexShader: passThroughVertexShader, + fragmentShader: blurFragmentShader, + }); + + const blurVerticalMaterial = new THREE.ShaderMaterial({ + uniforms: { + tInput: { value: null }, + dir: { value: new THREE.Vector2(0, 1) }, + res: { + value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), + }, + }, + vertexShader: passThroughVertexShader, + fragmentShader: blurFragmentShader, + }); + + const halftoneMaterial = new THREE.ShaderMaterial({ + transparent: true, + uniforms: { + tScene: { value: sceneTarget.texture }, + tGlow: { value: blurTargetB.texture }, + effectResolution: { + value: new THREE.Vector2(getRenderWidth(), getRenderHeight()), + }, + logicalResolution: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + tile: { value: initialSettings.halftone.scale }, + s_3: { value: initialSettings.halftone.power }, + s_4: { value: initialSettings.halftone.width }, + dashColor: { + value: new THREE.Color(initialSettings.halftone.dashColor), + }, + time: { value: 0 }, + waveAmount: { value: 0 }, + waveSpeed: { value: 1 }, + footprintScale: { value: 1.0 }, + interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, + interactionVelocity: { value: new THREE.Vector2(0, 0) }, + dragOffset: { value: new THREE.Vector2(0, 0) }, + hoverHalftonePowerShift: { value: 0 }, + hoverHalftoneRadius: { value: 0.2 }, + hoverHalftoneWidthShift: { value: 0 }, + hoverLightStrength: { value: 0 }, + hoverLightRadius: { value: 0.2 }, + hoverFlowStrength: { value: 0 }, + hoverFlowRadius: { value: 0.18 }, + dragFlowStrength: { value: 0 }, + cropToBounds: { value: 0 }, + }, + vertexShader: passThroughVertexShader, + fragmentShader: halftoneFragmentShader, + }); + + const blurHorizontalScene = new THREE.Scene(); + blurHorizontalScene.add( + new THREE.Mesh(fullScreenGeometry, blurHorizontalMaterial), + ); + + const blurVerticalScene = new THREE.Scene(); + blurVerticalScene.add( + new THREE.Mesh(fullScreenGeometry, blurVerticalMaterial), + ); + + const postScene = new THREE.Scene(); + postScene.add(new THREE.Mesh(fullScreenGeometry, halftoneMaterial)); + + const imageMaterial = new THREE.ShaderMaterial({ + uniforms: { + tImage: { value: null }, + imageSize: { value: new THREE.Vector2(1, 1) }, + viewportSize: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + zoom: { value: getImagePreviewZoom(initialPreviewDistance) }, + contrast: { value: initialSettings.halftone.imageContrast }, + }, + vertexShader: passThroughVertexShader, + fragmentShader: imagePassthroughFragmentShader, + }); + + const imageScene = new THREE.Scene(); + imageScene.add(new THREE.Mesh(fullScreenGeometry, imageMaterial)); + + const resources: SceneResources = { + ambientLight, + blurHorizontalMaterial, + blurHorizontalScene, + blurTargetA, + blurTargetB, + blurVerticalMaterial, + blurVerticalScene, + camera, + canvas, + fillLight, + fullScreenGeometry, + halftoneMaterial, + imageMaterial, + imageScene, + imageTexture: null, + materialAssets, + material, + mesh, + orthographicCamera, + postScene, + primaryLight, + renderer, + scene3d, + sceneTarget, + transmissionBacksideTarget, + transmissionTarget, + }; + + const updateViewportUniforms = ( + logicalWidth: number, + logicalHeight: number, + effectWidth: number, + effectHeight: number, + ) => { + blurHorizontalMaterial.uniforms.res.value.set( + effectWidth, + effectHeight, + ); + blurVerticalMaterial.uniforms.res.value.set(effectWidth, effectHeight); + halftoneMaterial.uniforms.effectResolution.value.set( + effectWidth, + effectHeight, + ); + halftoneMaterial.uniforms.logicalResolution.value.set( + logicalWidth, + logicalHeight, + ); + imageMaterial.uniforms.viewportSize.value.set( + logicalWidth, + logicalHeight, + ); + }; + + const getImageHalftoneScale = ( + viewportWidth: number, + viewportHeight: number, + activePreviewDistance: number, + ) => { + const imageSize = imageMaterial.uniforms.imageSize + .value as THREE.Vector2; + + return getImageFootprintScale({ + imageHeight: imageSize.y, + imageWidth: imageSize.x, + previewDistance: activePreviewDistance, + viewportHeight, + viewportWidth, + }); + }; + + const getMeshHalftoneScale = ( + viewportWidth: number, + viewportHeight: number, + lookAtTarget: THREE.Vector3, + ) => { + if (!mesh.geometry.boundingBox) { + mesh.geometry.computeBoundingBox(); + } + + const localBounds = mesh.geometry.boundingBox; + + if (!localBounds) { + return 1; + } + + mesh.updateMatrixWorld(); + camera.updateMatrixWorld(); + + return getMeshFootprintScale({ + camera, + localBounds, + lookAtTarget, + meshMatrixWorld: mesh.matrixWorld, + viewportHeight, + viewportWidth, + }); + }; + + resourcesReference.current = resources; + const latestGeometry = geometryReference.current; + + if (latestGeometry && resources.mesh.geometry !== latestGeometry) { + resources.mesh.geometry = latestGeometry; + } + + syncResources(resources, settingsReference.current); + + const captureSnapshot: HalftoneSnapshotFn = async ( + snapshotWidth: number, + snapshotHeight: number, + options, + ) => { + const activeSettings = settingsReference.current; + const isImage = + activeSettings.sourceMode === 'image' && + resources.imageTexture !== null; + const includeBackground = options?.includeBackground ?? false; + const backgroundColor = + options?.backgroundColor ?? activeSettings.background.color; + + const snapScene = createRenderTarget(snapshotWidth, snapshotHeight); + const snapTransmissionBackside = createRenderTarget( + snapshotWidth, + snapshotHeight, + ); + const snapTransmission = createRenderTarget( + snapshotWidth, + snapshotHeight, + ); + const snapBlurA = createRenderTarget(snapshotWidth, snapshotHeight); + const snapBlurB = createRenderTarget(snapshotWidth, snapshotHeight); + + const prevSize = renderer.getSize(new THREE.Vector2()); + + renderer.setSize(snapshotWidth, snapshotHeight, false); + + updateViewportUniforms( + snapshotWidth, + snapshotHeight, + snapshotWidth, + snapshotHeight, + ); + halftoneMaterial.uniforms.hoverHalftonePowerShift.value = 0; + halftoneMaterial.uniforms.hoverHalftoneWidthShift.value = 0; + halftoneMaterial.uniforms.hoverLightStrength.value = 0; + halftoneMaterial.uniforms.hoverFlowStrength.value = 0; + halftoneMaterial.uniforms.dragFlowStrength.value = 0; + halftoneMaterial.uniforms.interactionVelocity.value.set(0, 0); + halftoneMaterial.uniforms.dragOffset.value.set(0, 0); + halftoneMaterial.uniforms.cropToBounds.value = isImage ? 1 : 0; + + if (isImage) { + imageMaterial.uniforms.zoom.value = getImagePreviewZoom( + previewDistanceReference.current, + ); + halftoneMaterial.uniforms.footprintScale.value = + getImageHalftoneScale( + snapshotWidth, + snapshotHeight, + previewDistanceReference.current, + ); + } else { + camera.aspect = snapshotWidth / snapshotHeight; + camera.updateProjectionMatrix(); + halftoneMaterial.uniforms.footprintScale.value = getMeshHalftoneScale( + snapshotWidth, + snapshotHeight, + new THREE.Vector3(0, mesh.position.y * 0.2, 0), + ); + } + + if (isImage) { + renderer.setRenderTarget(snapScene); + renderer.render(imageScene, orthographicCamera); + } else { + renderHalftoneMaterialScene({ + camera, + elapsedTime: halftoneMaterial.uniforms.time.value as number, + material, + mesh, + outputTarget: snapScene, + renderer, + scene: scene3d, + transmissionBackground: materialAssets.glassBackgroundTexture, + transmissionScene: materialAssets.glassTransmissionScene, + transmissionBacksideTarget: snapTransmissionBackside, + transmissionTarget: snapTransmission, + }); + } + + halftoneMaterial.uniforms.tScene.value = snapScene.texture; + + blurHorizontalMaterial.uniforms.tInput.value = snapScene.texture; + renderer.setRenderTarget(snapBlurA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = snapBlurA.texture; + renderer.setRenderTarget(snapBlurB); + renderer.render(blurVerticalScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = snapBlurB.texture; + renderer.setRenderTarget(snapBlurA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = snapBlurA.texture; + renderer.setRenderTarget(snapBlurB); + renderer.render(blurVerticalScene, orthographicCamera); + + halftoneMaterial.uniforms.tGlow.value = snapBlurB.texture; + + const outputTarget = createRenderTarget(snapshotWidth, snapshotHeight); + renderer.setRenderTarget(outputTarget); + renderer.clear(); + renderer.render(postScene, orthographicCamera); + + const pixelBuffer = new Uint8Array(snapshotWidth * snapshotHeight * 4); + renderer.readRenderTargetPixels( + outputTarget, + 0, + 0, + snapshotWidth, + snapshotHeight, + pixelBuffer, + ); + + renderer.setSize(prevSize.x, prevSize.y, false); + halftoneMaterial.uniforms.tScene.value = sceneTarget.texture; + halftoneMaterial.uniforms.tGlow.value = blurTargetB.texture; + updateViewportUniforms( + getVirtualWidth(), + getVirtualHeight(), + prevSize.x, + prevSize.y, + ); + if (isImage) { + imageMaterial.uniforms.zoom.value = getImagePreviewZoom( + previewDistanceReference.current, + ); + } else { + camera.aspect = getWidth() / Math.max(getHeight(), 1); + camera.updateProjectionMatrix(); + } + + snapScene.dispose(); + snapTransmissionBackside.dispose(); + snapTransmission.dispose(); + snapBlurA.dispose(); + snapBlurB.dispose(); + outputTarget.dispose(); + + const flippedBuffer = new Uint8Array( + snapshotWidth * snapshotHeight * 4, + ); + const rowSize = snapshotWidth * 4; + for (let y = 0; y < snapshotHeight; y++) { + const srcOffset = y * rowSize; + const dstOffset = (snapshotHeight - 1 - y) * rowSize; + flippedBuffer.set( + pixelBuffer.subarray(srcOffset, srcOffset + rowSize), + dstOffset, + ); + } + + const fullSnapshotBounds = { + minX: 0, + minY: 0, + maxX: snapshotWidth - 1, + maxY: snapshotHeight - 1, + }; + const alphaCropBounds = getAlphaCropBounds( + flippedBuffer, + snapshotWidth, + snapshotHeight, + ); + const cropBounds = includeBackground + ? fullSnapshotBounds + : (alphaCropBounds ?? fullSnapshotBounds); + const croppedWidth = cropBounds.maxX - cropBounds.minX + 1; + const croppedHeight = cropBounds.maxY - cropBounds.minY + 1; + const croppedBuffer = new Uint8ClampedArray( + croppedWidth * croppedHeight * 4, + ); + + for (let y = 0; y < croppedHeight; y++) { + const sourceStart = + ((cropBounds.minY + y) * snapshotWidth + cropBounds.minX) * 4; + const sourceEnd = sourceStart + croppedWidth * 4; + const destinationStart = y * croppedWidth * 4; + + croppedBuffer.set( + flippedBuffer.subarray(sourceStart, sourceEnd), + destinationStart, + ); + } + + const imageData = new ImageData( + croppedBuffer, + croppedWidth, + croppedHeight, + ); + const offscreen = document.createElement('canvas'); + offscreen.width = croppedWidth; + offscreen.height = croppedHeight; + const ctx = offscreen.getContext('2d'); + + if (!ctx) { + return null; + } + + if (includeBackground) { + const sourceCanvas = document.createElement('canvas'); + sourceCanvas.width = croppedWidth; + sourceCanvas.height = croppedHeight; + const sourceContext = sourceCanvas.getContext('2d'); + + if (!sourceContext) { + return null; + } + + sourceContext.putImageData(imageData, 0, 0); + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, croppedWidth, croppedHeight); + ctx.drawImage(sourceCanvas, 0, 0); + } else { + ctx.putImageData(imageData, 0, 0); + } + + return new Promise((resolve) => { + offscreen.toBlob((blob) => resolve(blob), 'image/png'); + }); + }; + + if (activeSnapshotRef) { + activeSnapshotRef.current = captureSnapshot; + } + + const syncSize = () => { + if (cancelled) { + return; + } + + const width = getWidth(); + const height = getHeight(); + const logicalWidth = getVirtualWidth(); + const logicalHeight = getVirtualHeight(); + const renderWidth = getRenderWidth(); + const renderHeight = getRenderHeight(); + + renderer.setSize(renderWidth, renderHeight, false); + camera.aspect = width / height; + camera.updateProjectionMatrix(); + sceneTarget.setSize(renderWidth, renderHeight); + transmissionBacksideTarget.setSize(renderWidth, renderHeight); + transmissionTarget.setSize(renderWidth, renderHeight); + blurTargetA.setSize(renderWidth, renderHeight); + blurTargetB.setSize(renderWidth, renderHeight); + updateViewportUniforms( + logicalWidth, + logicalHeight, + renderWidth, + renderHeight, + ); + }; + + const resizeObserver = new ResizeObserver(syncSize); + resizeObserver.observe(container); + + const updatePointerPosition = ( + event: PointerEvent, + options?: { resetVelocity?: boolean }, + ) => { + const interaction = interactionReference.current; + const rect = canvas.getBoundingClientRect(); + const width = Math.max(rect.width, 1); + const height = Math.max(rect.height, 1); + + const nextMouseX = THREE.MathUtils.clamp( + (event.clientX - rect.left) / width, + 0, + 1, + ); + const nextMouseY = THREE.MathUtils.clamp( + (event.clientY - rect.top) / height, + 0, + 1, + ); + + const deltaX = nextMouseX - interaction.mouseX; + const deltaY = nextMouseY - interaction.mouseY; + + interaction.mouseX = nextMouseX; + interaction.mouseY = nextMouseY; + interaction.pointerInside = + interaction.dragging || + (event.clientX >= rect.left && + event.clientX <= rect.right && + event.clientY >= rect.top && + event.clientY <= rect.bottom); + + if (options?.resetVelocity) { + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + interaction.smoothedMouseX = nextMouseX; + interaction.smoothedMouseY = nextMouseY; + } else { + interaction.pointerVelocityX = deltaX; + interaction.pointerVelocityY = deltaY; + } + + return { deltaX, deltaY }; + }; + + const releasePointerCapture = (pointerId: number | null) => { + if (pointerId === null) { + return; + } + + if (!canvas.hasPointerCapture(pointerId)) { + return; + } + + try { + canvas.releasePointerCapture(pointerId); + } catch (error) { + void error; + } + }; + + const markFirstInteraction = () => { + if (didInteractReference.current) { + return; + } + + didInteractReference.current = true; + onFirstInteraction(); + }; + + const handlePointerDown = (event: PointerEvent) => { + const interaction = interactionReference.current; + const activeSettings = settingsReference.current; + const canDrag = + activeSettings.sourceMode === 'image' + ? false + : activeSettings.animation.followDragEnabled; + + updatePointerPosition(event, { resetVelocity: true }); + interaction.pointerX = event.clientX; + interaction.pointerY = event.clientY; + + if (canDrag) { + interaction.dragging = true; + interaction.activePointerId = event.pointerId; + interaction.velocityX = 0; + interaction.velocityY = 0; + + try { + canvas.setPointerCapture(event.pointerId); + } catch (error) { + void error; + } + } + + canvas.style.cursor = getCanvasCursor( + activeSettings, + interaction.dragging, + ); + + markFirstInteraction(); + }; + + const handlePointerMove = (event: PointerEvent) => { + const interaction = interactionReference.current; + const resetVelocity = + !interaction.pointerInside && !interaction.dragging; + updatePointerPosition( + event, + resetVelocity ? { resetVelocity: true } : undefined, + ); + const activeSettings = settingsReference.current; + + if ( + activeSettings.sourceMode === 'image' && + interaction.pointerInside + ) { + markFirstInteraction(); + } + + if (!interaction.dragging) { + return; + } + + if ( + interaction.activePointerId !== null && + event.pointerId !== interaction.activePointerId + ) { + return; + } + + const animation = activeSettings.animation; + + if (!animation.followDragEnabled) { + return; + } + + const deltaX = + (event.clientX - interaction.pointerX) * animation.dragSens; + const deltaY = + (event.clientY - interaction.pointerY) * animation.dragSens; + interaction.velocityX = deltaY; + interaction.velocityY = deltaX; + interaction.targetRotationY += deltaX; + interaction.targetRotationX += deltaY; + interaction.pointerX = event.clientX; + interaction.pointerY = event.clientY; + }; + + const handlePointerLeave = () => { + const interaction = interactionReference.current; + const activeSettings = settingsReference.current; + + if (interaction.dragging) { + return; + } + + interaction.pointerInside = false; + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + + if (activeSettings.sourceMode !== 'image') { + interaction.mouseX = 0.5; + interaction.mouseY = 0.5; + } + + canvas.style.cursor = getCanvasCursor(activeSettings, false); + }; + + const handlePointerUp = (event: PointerEvent) => { + const interaction = interactionReference.current; + const activeSettings = settingsReference.current; + const animation = activeSettings.animation; + + updatePointerPosition(event, { resetVelocity: true }); + releasePointerCapture(interaction.activePointerId); + interaction.activePointerId = null; + interaction.dragging = false; + const rect = canvas.getBoundingClientRect(); + interaction.pointerInside = + event.clientX >= rect.left && + event.clientX <= rect.right && + event.clientY >= rect.top && + event.clientY <= rect.bottom; + + if ( + !interaction.pointerInside && + activeSettings.sourceMode !== 'image' + ) { + interaction.mouseX = 0.5; + interaction.mouseY = 0.5; + } + + canvas.style.cursor = getCanvasCursor(activeSettings, false); + + if (!animation.springReturnEnabled) { + return; + } + + const springImpulse = Math.max(animation.springStrength * 10, 1.2); + interaction.rotationVelocityX += interaction.velocityX * springImpulse; + interaction.rotationVelocityY += interaction.velocityY * springImpulse; + interaction.rotationVelocityZ += + interaction.velocityY * springImpulse * 0.12; + interaction.targetRotationX = 0; + interaction.targetRotationY = 0; + interaction.velocityX = 0; + interaction.velocityY = 0; + }; + + const handlePointerCancel = () => { + const interaction = interactionReference.current; + const activeSettings = settingsReference.current; + releasePointerCapture(interaction.activePointerId); + interaction.activePointerId = null; + interaction.dragging = false; + interaction.pointerInside = false; + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + + interaction.mouseX = 0.5; + interaction.mouseY = 0.5; + interaction.smoothedMouseX = 0.5; + interaction.smoothedMouseY = 0.5; + + canvas.style.cursor = getCanvasCursor(activeSettings, false); + }; + + const handleWindowBlur = () => { + handlePointerCancel(); + }; + + canvas.addEventListener('pointermove', handlePointerMove); + canvas.addEventListener('pointerleave', handlePointerLeave); + canvas.addEventListener('pointerup', handlePointerUp); + canvas.addEventListener('pointercancel', handlePointerCancel); + window.addEventListener('blur', handleWindowBlur); + canvas.addEventListener('pointerdown', handlePointerDown); + + const clock = new THREE.Timer(); + clock.connect(document); + + const renderFrame = (timestamp?: DOMHighResTimeStamp) => { + if (cancelled) { + return; + } + + animationFrameId = window.requestAnimationFrame(renderFrame); + clock.update(timestamp); + + const interaction = interactionReference.current; + const activeSettings = settingsReference.current; + const delta = clock.getDelta(); + const elapsedTime = + (initialPoseReference.current?.timeElapsed ?? 0) + clock.getElapsed(); + const baseDistance = previewDistanceReference.current; + const logicalWidth = getVirtualWidth(); + const logicalHeight = getVirtualHeight(); + const isImageMode = activeSettings.sourceMode === 'image'; + const hasImageTexture = resources.imageTexture !== null; + + halftoneMaterial.uniforms.time.value = elapsedTime; + halftoneMaterial.uniforms.waveAmount.value = + activeSettings.animation.waveEnabled && !isImageMode + ? activeSettings.animation.waveAmount + : 0; + halftoneMaterial.uniforms.waveSpeed.value = + activeSettings.animation.waveSpeed; + + if (isImageMode && !hasImageTexture) { + renderer.setRenderTarget(null); + renderer.clear(); + return; + } + + halftoneMaterial.uniforms.cropToBounds.value = isImageMode ? 1 : 0; + + if (isImageMode) { + const pointerActive = interaction.pointerInside; + + interaction.smoothedMouseX += + (interaction.mouseX - interaction.smoothedMouseX) * + IMAGE_POINTER_FOLLOW; + interaction.smoothedMouseY += + (interaction.mouseY - interaction.smoothedMouseY) * + IMAGE_POINTER_FOLLOW; + interaction.pointerVelocityX *= IMAGE_POINTER_VELOCITY_DAMPING; + interaction.pointerVelocityY *= IMAGE_POINTER_VELOCITY_DAMPING; + + halftoneMaterial.uniforms.interactionUv.value.set( + interaction.smoothedMouseX, + 1 - interaction.smoothedMouseY, + ); + halftoneMaterial.uniforms.interactionVelocity.value.set( + interaction.pointerVelocityX * logicalWidth, + -interaction.pointerVelocityY * logicalHeight, + ); + halftoneMaterial.uniforms.dragOffset.value.set(0, 0); + halftoneMaterial.uniforms.hoverHalftonePowerShift.value = + pointerActive && activeSettings.animation.hoverHalftoneEnabled + ? activeSettings.animation.hoverHalftonePowerShift + : 0; + halftoneMaterial.uniforms.hoverHalftoneRadius.value = + activeSettings.animation.hoverHalftoneRadius; + halftoneMaterial.uniforms.hoverHalftoneWidthShift.value = + pointerActive && activeSettings.animation.hoverHalftoneEnabled + ? activeSettings.animation.hoverHalftoneWidthShift + : 0; + halftoneMaterial.uniforms.hoverLightStrength.value = + pointerActive && activeSettings.animation.hoverLightEnabled + ? activeSettings.animation.hoverLightIntensity + : 0; + halftoneMaterial.uniforms.hoverLightRadius.value = + activeSettings.animation.hoverLightRadius; + halftoneMaterial.uniforms.hoverFlowStrength.value = 0; + halftoneMaterial.uniforms.hoverFlowRadius.value = 0.18; + halftoneMaterial.uniforms.dragFlowStrength.value = 0; + + imageMaterial.uniforms.zoom.value = getImagePreviewZoom(baseDistance); + imageMaterial.uniforms.viewportSize.value.set( + logicalWidth, + logicalHeight, + ); + halftoneMaterial.uniforms.footprintScale.value = + getImageHalftoneScale(logicalWidth, logicalHeight, baseDistance); + + poseChangeReference.current({ + autoElapsed: 0, + rotateElapsed: 0, + rotationX: 0, + rotationY: 0, + rotationZ: 0, + targetRotationX: interaction.targetRotationX, + targetRotationY: interaction.targetRotationY, + timeElapsed: elapsedTime, + }); + + if (!activeSettings.halftone.enabled) { + renderer.setRenderTarget(null); + renderer.clear(); + renderer.render(imageScene, orthographicCamera); + return; + } + + renderer.setRenderTarget(sceneTarget); + renderer.render(imageScene, orthographicCamera); + } + + if (!isImageMode) { + let baseRotationX = 0; + let baseRotationY = 0; + let baseRotationZ = 0; + let meshOffsetY = 0; + let meshScale = 1; + let lightAngle = activeSettings.lighting.angleDegrees; + let lightHeight = activeSettings.lighting.height; + + if (activeSettings.animation.autoRotateEnabled) { + interaction.autoElapsed += delta; + baseRotationY += + interaction.autoElapsed * activeSettings.animation.autoSpeed; + baseRotationX += + Math.sin(interaction.autoElapsed * 0.2) * + activeSettings.animation.autoWobble; + } + + if (activeSettings.animation.floatEnabled) { + const floatPhase = + elapsedTime * activeSettings.animation.floatSpeed; + const driftAmount = + (activeSettings.animation.driftAmount * Math.PI) / 180; + + meshOffsetY += + Math.sin(floatPhase) * activeSettings.animation.floatAmplitude; + baseRotationX += Math.sin(floatPhase * 0.72) * driftAmount * 0.45; + baseRotationZ += Math.cos(floatPhase * 0.93) * driftAmount * 0.3; + } + + if (activeSettings.animation.breatheEnabled) { + meshScale *= + 1 + + Math.sin(elapsedTime * activeSettings.animation.breatheSpeed) * + activeSettings.animation.breatheAmount; + } + + if (activeSettings.animation.rotateEnabled) { + interaction.rotateElapsed += delta; + const rotateProgress = activeSettings.animation.rotatePingPong + ? Math.sin( + interaction.rotateElapsed * + activeSettings.animation.rotateSpeed, + ) * Math.PI + : interaction.rotateElapsed * + activeSettings.animation.rotateSpeed; + + if (activeSettings.animation.rotatePreset === 'axis') { + const axisDirection = + activeSettings.animation.rotateAxis.startsWith('-') ? -1 : 1; + const axisProgress = rotateProgress * axisDirection; + + if ( + activeSettings.animation.rotateAxis === 'x' || + activeSettings.animation.rotateAxis === 'xy' || + activeSettings.animation.rotateAxis === '-x' || + activeSettings.animation.rotateAxis === '-xy' + ) { + baseRotationX += axisProgress; + } + + if ( + activeSettings.animation.rotateAxis === 'y' || + activeSettings.animation.rotateAxis === 'xy' || + activeSettings.animation.rotateAxis === '-y' || + activeSettings.animation.rotateAxis === '-xy' + ) { + baseRotationY += axisProgress; + } + + if ( + activeSettings.animation.rotateAxis === 'z' || + activeSettings.animation.rotateAxis === '-z' + ) { + baseRotationZ += axisProgress; + } + } else if (activeSettings.animation.rotatePreset === 'lissajous') { + baseRotationX += Math.sin(rotateProgress * 0.85) * 0.65; + baseRotationY += Math.sin(rotateProgress * 1.35 + 0.8) * 1.05; + baseRotationZ += Math.sin(rotateProgress * 0.55 + 1.6) * 0.32; + } else if (activeSettings.animation.rotatePreset === 'orbit') { + baseRotationX += Math.sin(rotateProgress * 0.75) * 0.42; + baseRotationY += Math.cos(rotateProgress) * 1.2; + baseRotationZ += Math.sin(rotateProgress * 1.25) * 0.24; + } else if (activeSettings.animation.rotatePreset === 'tumble') { + baseRotationX += rotateProgress * 0.55; + baseRotationY += Math.sin(rotateProgress * 0.8) * 0.9; + baseRotationZ += Math.cos(rotateProgress * 1.1) * 0.38; + } + } + + if (activeSettings.animation.lightSweepEnabled) { + const lightPhase = + elapsedTime * activeSettings.animation.lightSweepSpeed; + lightAngle += + Math.sin(lightPhase) * activeSettings.animation.lightSweepRange; + lightHeight += + Math.cos(lightPhase * 0.85) * + activeSettings.animation.lightSweepHeightRange; + } + + let targetX = baseRotationX; + let targetY = baseRotationY; + let easing = 0.12; + + if (activeSettings.animation.followHoverEnabled) { + const rangeRadians = + (activeSettings.animation.hoverRange * Math.PI) / 180; + + if ( + activeSettings.animation.hoverReturn || + interaction.mouseX !== 0.5 || + interaction.mouseY !== 0.5 + ) { + targetX += (interaction.mouseY - 0.5) * rangeRadians; + targetY += (interaction.mouseX - 0.5) * rangeRadians; + } + + easing = activeSettings.animation.hoverEase; + } + + if (activeSettings.animation.followDragEnabled) { + if ( + !interaction.dragging && + activeSettings.animation.dragMomentum + ) { + interaction.targetRotationX += interaction.velocityX; + interaction.targetRotationY += interaction.velocityY; + interaction.velocityX *= + 1 - activeSettings.animation.dragFriction; + interaction.velocityY *= + 1 - activeSettings.animation.dragFriction; + } + + targetX += interaction.targetRotationX; + targetY += interaction.targetRotationY; + easing = activeSettings.animation.dragFriction; + } + + if ( + activeSettings.animation.autoRotateEnabled && + !activeSettings.animation.followHoverEnabled && + !activeSettings.animation.followDragEnabled + ) { + targetX = baseRotationX + interaction.targetRotationX; + targetY = baseRotationY + interaction.targetRotationY; + + if (interaction.dragging) { + targetX = interaction.targetRotationX; + targetY = interaction.targetRotationY; + } + + easing = 0.08; + } + + if (activeSettings.animation.springReturnEnabled) { + const springX = applySpringStep( + interaction.rotationX, + targetX, + interaction.rotationVelocityX, + activeSettings.animation.springStrength, + activeSettings.animation.springDamping, + ); + const springY = applySpringStep( + interaction.rotationY, + targetY, + interaction.rotationVelocityY, + activeSettings.animation.springStrength, + activeSettings.animation.springDamping, + ); + const springZ = applySpringStep( + interaction.rotationZ, + baseRotationZ, + interaction.rotationVelocityZ, + activeSettings.animation.springStrength, + activeSettings.animation.springDamping, + ); + + interaction.rotationX = springX.value; + interaction.rotationY = springY.value; + interaction.rotationZ = springZ.value; + interaction.rotationVelocityX = springX.velocity; + interaction.rotationVelocityY = springY.velocity; + interaction.rotationVelocityZ = springZ.velocity; + } else { + interaction.rotationX += (targetX - interaction.rotationX) * easing; + interaction.rotationY += (targetY - interaction.rotationY) * easing; + interaction.rotationZ += + (baseRotationZ - interaction.rotationZ) * + (activeSettings.animation.rotatePingPong ? 0.18 : 0.12); + } + + mesh.rotation.set( + interaction.rotationX, + interaction.rotationY, + interaction.rotationZ, + ); + mesh.position.y = meshOffsetY; + mesh.scale.setScalar(meshScale); + + if (activeSettings.animation.cameraParallaxEnabled) { + const cameraRange = activeSettings.animation.cameraParallaxAmount; + const cameraEase = activeSettings.animation.cameraParallaxEase; + const centeredX = (interaction.mouseX - 0.5) * 2; + const centeredY = (0.5 - interaction.mouseY) * 2; + const orbitYaw = centeredX * cameraRange; + const orbitPitch = centeredY * cameraRange * 0.7; + const horizontalRadius = Math.cos(orbitPitch) * baseDistance; + const targetCameraX = Math.sin(orbitYaw) * horizontalRadius; + const targetCameraY = Math.sin(orbitPitch) * baseDistance * 0.85; + const targetCameraZ = Math.cos(orbitYaw) * horizontalRadius; + + camera.position.x += + (targetCameraX - camera.position.x) * cameraEase; + camera.position.y += + (targetCameraY - camera.position.y) * cameraEase; + camera.position.z += + (targetCameraZ - camera.position.z) * cameraEase; + } else { + camera.position.x += (0 - camera.position.x) * 0.12; + camera.position.y += (0 - camera.position.y) * 0.12; + camera.position.z += (baseDistance - camera.position.z) * 0.12; + } + + const lookAtTarget = new THREE.Vector3(0, meshOffsetY * 0.2, 0); + + camera.lookAt(lookAtTarget); + setPrimaryLightPosition(primaryLight, lightAngle, lightHeight); + halftoneMaterial.uniforms.footprintScale.value = getMeshHalftoneScale( + logicalWidth, + logicalHeight, + lookAtTarget, + ); + + poseChangeReference.current({ + autoElapsed: interaction.autoElapsed, + rotateElapsed: interaction.rotateElapsed, + rotationX: interaction.rotationX, + rotationY: interaction.rotationY, + rotationZ: interaction.rotationZ, + targetRotationX: interaction.targetRotationX, + targetRotationY: interaction.targetRotationY, + timeElapsed: elapsedTime, + }); + + if (!activeSettings.halftone.enabled) { + renderHalftoneMaterialScene({ + camera, + elapsedTime, + material, + mesh, + outputTarget: null, + renderer, + scene: scene3d, + transmissionBackground: materialAssets.glassBackgroundTexture, + transmissionScene: materialAssets.glassTransmissionScene, + transmissionBacksideTarget, + transmissionTarget, + }); + return; + } + + renderHalftoneMaterialScene({ + camera, + elapsedTime, + material, + mesh, + outputTarget: sceneTarget, + renderer, + scene: scene3d, + transmissionBackground: materialAssets.glassBackgroundTexture, + transmissionScene: materialAssets.glassTransmissionScene, + transmissionBacksideTarget, + transmissionTarget, + }); + halftoneMaterial.uniforms.hoverHalftonePowerShift.value = 0; + halftoneMaterial.uniforms.hoverHalftoneWidthShift.value = 0; + halftoneMaterial.uniforms.hoverLightStrength.value = 0; + halftoneMaterial.uniforms.hoverFlowStrength.value = 0; + halftoneMaterial.uniforms.dragFlowStrength.value = 0; + halftoneMaterial.uniforms.interactionVelocity.value.set(0, 0); + halftoneMaterial.uniforms.dragOffset.value.set(0, 0); + } + + blurHorizontalMaterial.uniforms.tInput.value = sceneTarget.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = blurTargetB.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + renderer.setRenderTarget(null); + renderer.clear(); + renderer.render(postScene, orthographicCamera); + }; + + renderFrame(); + + cleanup = () => { + resizeObserver.disconnect(); + canvas.removeEventListener('pointermove', handlePointerMove); + canvas.removeEventListener('pointerleave', handlePointerLeave); + canvas.removeEventListener('pointerup', handlePointerUp); + canvas.removeEventListener('pointercancel', handlePointerCancel); + window.removeEventListener('blur', handleWindowBlur); + canvas.removeEventListener('pointerdown', handlePointerDown); + window.cancelAnimationFrame(animationFrameId); + clock.dispose(); + + blurHorizontalMaterial.dispose(); + blurVerticalMaterial.dispose(); + halftoneMaterial.dispose(); + imageMaterial.dispose(); + + if (resources.imageTexture) { + resources.imageTexture.dispose(); + } + + fullScreenGeometry.dispose(); + material.dispose(); + sceneTarget.dispose(); + transmissionBacksideTarget.dispose(); + transmissionTarget.dispose(); + blurTargetA.dispose(); + blurTargetB.dispose(); + disposeHalftoneMaterialAssets(materialAssets); + renderer.dispose(); + resourcesReference.current = null; + + if (activeSnapshotRef) { + activeSnapshotRef.current = null; + } + + if (canvas.parentNode === container) { + container.removeChild(canvas); + } + }; + })(); + + return () => { + cancelled = true; + cleanup(); + }; }, [onFirstInteraction]); return ( diff --git a/packages/twenty-website-new/src/app/halftone/_components/HalftoneStudio.tsx b/packages/twenty-website-new/src/app/halftone/_components/HalftoneStudio.tsx index b8eae98168..0d23fb8ff6 100644 --- a/packages/twenty-website-new/src/app/halftone/_components/HalftoneStudio.tsx +++ b/packages/twenty-website-new/src/app/halftone/_components/HalftoneStudio.tsx @@ -5,6 +5,7 @@ import { type HalftoneSnapshotFn, } from '@/app/halftone/_components/HalftoneCanvas'; import { ControlsPanel } from '@/app/halftone/_components/ControlsPanel'; +import { resolveExportArtifactNames } from '@/app/halftone/_lib/exportNames'; import { createFallbackGeometry, disposeGeometryCache, @@ -354,6 +355,10 @@ export function HalftoneStudio() { selectedShape, state.settings.sourceMode, ]); + const exportArtifactNames = useMemo( + () => resolveExportArtifactNames(exportName, defaultExportName), + [defaultExportName, exportName], + ); useEffect(() => { halftoneBySourceModeReference.current[state.settings.sourceMode] = { @@ -818,10 +823,8 @@ export function HalftoneStudio() { }, []); const handleExportReact = useCallback(() => { - const componentName = exportName || defaultExportName; - const kebabName = componentName - .replace(/([a-z])([A-Z])/g, '$1-$2') - .toLowerCase(); + const componentName = exportArtifactNames.componentName; + const kebabName = exportArtifactNames.fileBaseName; const isImageMode = state.settings.sourceMode === 'image'; const importedFile = selectedImportedFile; const exportBackgroundColor = exportBackground @@ -857,8 +860,7 @@ export function HalftoneStudio() { downloadBlob(modelFilename ?? importedFile.name, importedFile); } }, [ - defaultExportName, - exportName, + exportArtifactNames, exportBackground, imageFile, previewDistance, @@ -870,10 +872,7 @@ export function HalftoneStudio() { const handleExportHalftoneImage = useCallback( async (width: number, height: number) => { const snapshotFn = snapshotReference.current; - const componentName = exportName || defaultExportName; - const kebabName = componentName - .replace(/([a-z])([A-Z])/g, '$1-$2') - .toLowerCase(); + const kebabName = exportArtifactNames.fileBaseName; if (!snapshotFn) { return; @@ -891,18 +890,15 @@ export function HalftoneStudio() { downloadBlob(`${kebabName}-${width}x${height}.png`, blob); }, [ - defaultExportName, + exportArtifactNames.fileBaseName, exportBackground, - exportName, state.settings.background.color, ], ); const handleExportHtml = useCallback(async () => { - const componentName = exportName || defaultExportName; - const kebabName = componentName - .replace(/([a-z])([A-Z])/g, '$1-$2') - .toLowerCase(); + const componentName = exportArtifactNames.componentName; + const kebabName = exportArtifactNames.fileBaseName; const isImageMode = state.settings.sourceMode === 'image'; const importedFile = selectedImportedFile; const exportBackgroundColor = exportBackground @@ -936,8 +932,7 @@ export function HalftoneStudio() { downloadBlob(imageExportFilename ?? imageFile.name, imageFile); } }, [ - defaultExportName, - exportName, + exportArtifactNames, exportBackground, imageFile, previewDistance, diff --git a/packages/twenty-website-new/src/app/halftone/_components/controls/AnimationsTab.tsx b/packages/twenty-website-new/src/app/halftone/_components/controls/AnimationsTab.tsx index ed1140bbd7..6351a9bfa5 100644 --- a/packages/twenty-website-new/src/app/halftone/_components/controls/AnimationsTab.tsx +++ b/packages/twenty-website-new/src/app/halftone/_components/controls/AnimationsTab.tsx @@ -18,6 +18,9 @@ import { ToggleControl, } from './controls-ui'; +const MIN_ROTATION_SPEED = 0.01; +const ROTATION_SPEED_STEP = 0.01; + type AnimationsTabProps = { onAnimationSettingsChange: ( value: Partial, @@ -41,6 +44,75 @@ export function AnimationsTab({ {isImageMode ? ( <>
+ + onAnimationSettingsChange({ + hoverHalftoneEnabled: event.target.checked, + }) + } + preserveCase + > + {effectLabel( + 'Hover Halftone', + 'Uses the cursor radius to locally push the halftone power and width, so the bars open or tighten around the mouse instead of only brightening.', + )} + + {animation.hoverHalftoneEnabled ? ( + + + onAnimationSettingsChange({ + hoverHalftonePowerShift: Number(event.target.value), + }) + } + step={0.01} + value={animation.hoverHalftonePowerShift} + valueLabel={formatDecimal( + animation.hoverHalftonePowerShift, + 2, + )} + > + Power shift + + + onAnimationSettingsChange({ + hoverHalftoneWidthShift: Number(event.target.value), + }) + } + step={0.01} + value={animation.hoverHalftoneWidthShift} + valueLabel={formatDecimal( + animation.hoverHalftoneWidthShift, + 2, + )} + > + Width shift + + + onAnimationSettingsChange({ + hoverHalftoneRadius: Number(event.target.value), + }) + } + step={0.01} + value={animation.hoverHalftoneRadius} + valueLabel={formatDecimal(animation.hoverHalftoneRadius, 2)} + > + Radius + + + ) : null} +
+ +
@@ -110,15 +182,15 @@ export function AnimationsTab({ <> onAnimationSettingsChange({ autoSpeed: Number(event.target.value), }) } - step={0.05} + step={ROTATION_SPEED_STEP} value={animation.autoSpeed} - valueLabel={formatDecimal(animation.autoSpeed, 1)} + valueLabel={formatDecimal(animation.autoSpeed, 2)} > Speed @@ -195,15 +267,15 @@ export function AnimationsTab({ ) : null} onAnimationSettingsChange({ rotateSpeed: Number(event.target.value), }) } - step={0.1} + step={ROTATION_SPEED_STEP} value={animation.rotateSpeed} - valueLabel={formatDecimal(animation.rotateSpeed, 1)} + valueLabel={formatDecimal(animation.rotateSpeed, 2)} > Speed diff --git a/packages/twenty-website-new/src/app/halftone/_components/controls/DesignTab.tsx b/packages/twenty-website-new/src/app/halftone/_components/controls/DesignTab.tsx index ffa25adc29..5ee6f87108 100644 --- a/packages/twenty-website-new/src/app/halftone/_components/controls/DesignTab.tsx +++ b/packages/twenty-website-new/src/app/halftone/_components/controls/DesignTab.tsx @@ -1,10 +1,16 @@ 'use client'; import { formatAngle, formatDecimal } from '@/app/halftone/_lib/formatters'; -import type { - HalftoneBackgroundSettings, - HalftoneSourceMode, - HalftoneStudioSettings, +import { + DEFAULT_GLASS_ANIMATION_SETTINGS, + DEFAULT_GLASS_LIGHTING_SETTINGS, + DEFAULT_GLASS_MATERIAL_SETTINGS, + DEFAULT_SOLID_ANIMATION_SETTINGS, + DEFAULT_SOLID_LIGHTING_SETTINGS, + DEFAULT_SOLID_MATERIAL_SETTINGS, + type HalftoneBackgroundSettings, + type HalftoneSourceMode, + type HalftoneStudioSettings, } from '@/app/halftone/_lib/state'; import { styled } from '@linaria/react'; import { @@ -15,6 +21,7 @@ import { Section, SectionTitle, SectionToggleHeader, + SegmentedControl, SelectInput, ShapeRow, SliderControl, @@ -59,6 +66,9 @@ const ColorSwapButton = styled.button` `; type DesignTabProps = { + onAnimationSettingsChange: ( + value: Partial, + ) => void; imageFileName: string | null; onBackgroundChange: (value: Partial) => void; onDashColorChange: (value: string) => void; @@ -80,7 +90,14 @@ type DesignTabProps = { shapeOptions: Array<{ label: string; value: string }>; }; +function matchesSettings(value: T, target: T) { + return (Object.keys(target) as Array).every( + (key) => value[key] === target[key], + ); +} + export function DesignTab({ + onAnimationSettingsChange, imageFileName, onBackgroundChange, onDashColorChange, @@ -103,6 +120,39 @@ export function DesignTab({ imageFileName === null || imageFileName === DEFAULT_IMAGE_FILE_NAME ? DEFAULT_IMAGE_OPTION_LABEL : imageFileName; + + const handleSurfaceChange = (surface: 'glass' | 'solid') => { + const switchingToGlass = surface === 'glass'; + + onMaterialChange( + switchingToGlass + ? DEFAULT_GLASS_MATERIAL_SETTINGS + : DEFAULT_SOLID_MATERIAL_SETTINGS, + ); + + if (switchingToGlass) { + if (matchesSettings(settings.lighting, DEFAULT_SOLID_LIGHTING_SETTINGS)) { + onLightingChange(DEFAULT_GLASS_LIGHTING_SETTINGS); + } + + if ( + matchesSettings(settings.animation, DEFAULT_SOLID_ANIMATION_SETTINGS) + ) { + onAnimationSettingsChange(DEFAULT_GLASS_ANIMATION_SETTINGS); + } + + return; + } + + if (matchesSettings(settings.lighting, DEFAULT_GLASS_LIGHTING_SETTINGS)) { + onLightingChange(DEFAULT_SOLID_LIGHTING_SETTINGS); + } + + if (matchesSettings(settings.animation, DEFAULT_GLASS_ANIMATION_SETTINGS)) { + onAnimationSettingsChange(DEFAULT_SOLID_ANIMATION_SETTINGS); + } + }; + const handleSwapColors = () => { const nextDashColor = settings.background.color; const nextBackgroundColor = settings.halftone.dashColor; @@ -280,6 +330,18 @@ export function DesignTab({
Material + + handleSurfaceChange(value === 'glass' ? 'glass' : 'solid') + } + options={[ + { label: 'Solid', value: 'solid' }, + { label: 'Glass', value: 'glass' }, + ]} + value={settings.material.surface} + > + Surface + Metalness + {settings.material.surface === 'glass' ? ( + <> + + onMaterialChange({ + thickness: Number(event.target.value), + }) + } + step={0.1} + value={settings.material.thickness} + valueLabel={formatDecimal(settings.material.thickness, 0)} + > + Thickness + + + onMaterialChange({ + refraction: Number(event.target.value), + }) + } + step={0.01} + value={settings.material.refraction} + valueLabel={formatDecimal(settings.material.refraction)} + > + Refraction + + + onMaterialChange({ + environmentPower: Number(event.target.value), + }) + } + step={0.01} + value={settings.material.environmentPower} + valueLabel={formatDecimal( + settings.material.environmentPower, + 2, + )} + > + Power + + + ) : null}
diff --git a/packages/twenty-website-new/src/app/halftone/_components/controls/ExportTab.tsx b/packages/twenty-website-new/src/app/halftone/_components/controls/ExportTab.tsx index 0f734e9fb5..b1f53a0d5b 100644 --- a/packages/twenty-website-new/src/app/halftone/_components/controls/ExportTab.tsx +++ b/packages/twenty-website-new/src/app/halftone/_components/controls/ExportTab.tsx @@ -1,5 +1,6 @@ 'use client'; +import { resolveExportArtifactNames } from '@/app/halftone/_lib/exportNames'; import { formatAnimationName } from '@/app/halftone/_lib/formatters'; import type { HalftoneGeometrySpec, @@ -72,8 +73,11 @@ export function ExportTab({ ? DEFAULT_IMAGE_LABEL : imageFileName : (selectedShape?.label ?? settings.shapeKey); - - const componentName = exportName || defaultExportName; + const inputName = exportName || defaultExportName; + const { componentName } = resolveExportArtifactNames( + exportName, + defaultExportName, + ); const handleDownloadHalftoneImage = () => { const [widthStr, heightStr] = resolution.split('x'); @@ -98,7 +102,7 @@ export function ExportTab({ onFocus={(event) => event.currentTarget.select()} placeholder={defaultExportName} type="text" - value={componentName} + value={inputName} /> ` + background: ${(props) => + props.$active ? 'rgba(255, 255, 255, 0.14)' : 'transparent'}; + border: none; + border-radius: 6px; + color: ${(props) => + props.$active ? 'rgba(255, 255, 255, 0.94)' : 'rgba(255, 255, 255, 0.58)'}; + cursor: pointer; + font-family: ${theme.font.family.sans}; + font-size: 11px; + font-weight: ${(props) => (props.$active ? 600 : 500)}; + height: 100%; + padding: 0 10px; + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover { + color: rgba(255, 255, 255, 0.86); + } + + &:focus-visible { + outline: 1px solid rgba(255, 255, 255, 0.35); + outline-offset: 1px; + } +`; + const EditableControlValueButton = styled.button` background: transparent; border: none; @@ -250,8 +298,9 @@ export const SelectInput = styled.select` cursor: pointer; font-family: ${theme.font.family.sans}; font-size: 11px; + height: 24px; outline: none; - padding: 7px 34px 7px 10px; + padding: 0 34px 0 10px; transition: border-color 0.15s ease; width: 100%; @@ -576,10 +625,10 @@ export const UploadButton = styled.button` display: flex; flex-shrink: 0; font-size: 13px; - height: 32px; + height: 24px; justify-content: center; transition: all 0.15s ease; - width: 32px; + width: 24px; &:hover { background: rgba(255, 255, 255, 0.12); @@ -874,6 +923,51 @@ export function SelectControl({ ); } +type SegmentedControlProps = { + children: ReactNode; + onChange: (value: string) => void; + value: string; + options: Array<{ label: string; value: string }>; +}; + +export function SegmentedControl({ + children, + onChange, + options, + value, +}: SegmentedControlProps) { + return ( + + {children} + + {options.map((option) => { + const isActive = option.value === value; + + return ( + { + if (!isActive) { + onChange(option.value); + } + }} + role="radio" + type="button" + > + {option.label} + + ); + })} + + + ); +} + type ToggleControlProps = { checked: boolean; label: ReactNode; diff --git a/packages/twenty-website-new/src/app/halftone/_lib/exportNames.ts b/packages/twenty-website-new/src/app/halftone/_lib/exportNames.ts new file mode 100644 index 0000000000..c81dec8f6a --- /dev/null +++ b/packages/twenty-website-new/src/app/halftone/_lib/exportNames.ts @@ -0,0 +1,51 @@ +function toPascalCase(value: string) { + const tokens = value + .replace(/\.[^.]+$/, '') + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/[^a-zA-Z0-9]+/g, ' ') + .trim() + .split(/\s+/) + .filter(Boolean); + + const joined = tokens + .map((token) => token.charAt(0).toUpperCase() + token.slice(1)) + .join(''); + + if (!joined) { + return 'HalftoneDashes'; + } + + return /^[A-Za-z_]/.test(joined) ? joined : `Halftone${joined}`; +} + +export function normalizeExportComponentName( + value: string | null | undefined, + fallback = 'HalftoneDashes', +) { + return toPascalCase(value?.trim() || fallback); +} + +export function toKebabCase(value: string) { + const normalized = value + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') + .replace(/([a-zA-Z])([0-9])/g, '$1-$2') + .replace(/([0-9])([a-zA-Z])/g, '$1-$2') + .replace(/[^a-zA-Z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .toLowerCase(); + + return normalized || 'halftone-dashes'; +} + +export function resolveExportArtifactNames( + value: string | null | undefined, + fallback = 'HalftoneDashes', +) { + const componentName = normalizeExportComponentName(value, fallback); + + return { + componentName, + fileBaseName: toKebabCase(componentName), + }; +} diff --git a/packages/twenty-website-new/src/app/halftone/_lib/exporters.test.ts b/packages/twenty-website-new/src/app/halftone/_lib/exporters.test.ts new file mode 100644 index 0000000000..ac35047609 --- /dev/null +++ b/packages/twenty-website-new/src/app/halftone/_lib/exporters.test.ts @@ -0,0 +1,37 @@ +import { + generateReactComponent, + generateStandaloneHtml, +} from '@/app/halftone/_lib/exporters'; +import { resolveExportArtifactNames } from '@/app/halftone/_lib/exportNames'; +import { DEFAULT_HALFTONE_SETTINGS } from '@/app/halftone/_lib/state'; + +describe('halftone export naming', () => { + it('normalizes free-form export names into safe component and file names', () => { + expect(resolveExportArtifactNames('hero export 2026')).toEqual({ + componentName: 'HeroExport2026', + fileBaseName: 'hero-export-2026', + }); + }); + + it('sanitizes generated React component identifiers', () => { + const output = generateReactComponent( + DEFAULT_HALFTONE_SETTINGS, + undefined, + 'hero export 2026', + ); + + expect(output).toContain('type HeroExport2026Props = {'); + expect(output).toContain('export default function HeroExport2026({'); + expect(output).not.toContain('type hero export 2026Props = {'); + }); + + it('sanitizes generated standalone HTML titles', async () => { + const output = await generateStandaloneHtml( + DEFAULT_HALFTONE_SETTINGS, + undefined, + 'hero export 2026', + ); + + expect(output).toContain('HeroExport2026'); + }); +}); diff --git a/packages/twenty-website-new/src/app/halftone/_lib/exporters.ts b/packages/twenty-website-new/src/app/halftone/_lib/exporters.ts index 6244665f9b..03085470e8 100644 --- a/packages/twenty-website-new/src/app/halftone/_lib/exporters.ts +++ b/packages/twenty-website-new/src/app/halftone/_lib/exporters.ts @@ -1,3 +1,4 @@ +import { normalizeExportComponentName } from '@/app/halftone/_lib/exportNames'; import { HALFTONE_FOOTPRINT_RUNTIME_SOURCE, REFERENCE_PREVIEW_DISTANCE, @@ -10,8 +11,9 @@ import { type HalftoneGeometrySpec, type HalftoneStudioSettings, } from '@/app/halftone/_lib/state'; +import { GLASS_ENVIRONMENT_DATA_URL } from '@/app/halftone/_lib/glassEnvironmentData'; -const passThroughVertexShader = /* glsl */ ` +const passThroughVertexShader = ` varying vec2 vUv; void main() { @@ -20,7 +22,7 @@ const passThroughVertexShader = /* glsl */ ` } `; -const blurFragmentShader = /* glsl */ ` +const blurFragmentShader = ` precision highp float; uniform sampler2D tInput; @@ -52,7 +54,7 @@ const blurFragmentShader = /* glsl */ ` } `; -const imagePassthroughFragmentShader = /* glsl */ ` +const imagePassthroughFragmentShader = ` precision highp float; uniform sampler2D tImage; @@ -69,7 +71,6 @@ const imagePassthroughFragmentShader = /* glsl */ ` vec2 uv = vUv; - // Contain: show full image, letterbox/pillarbox as needed if (imageAspect > viewAspect) { float scale = viewAspect / imageAspect; uv.y = (uv.y - 0.5) / scale + 0.5; @@ -90,7 +91,7 @@ const imagePassthroughFragmentShader = /* glsl */ ` } `; -const halftoneFragmentShader = /* glsl */ ` +const halftoneFragmentShader = ` precision highp float; uniform sampler2D tScene; @@ -108,6 +109,9 @@ const halftoneFragmentShader = /* glsl */ ` uniform vec2 interactionUv; uniform vec2 interactionVelocity; uniform vec2 dragOffset; + uniform float hoverHalftonePowerShift; + uniform float hoverHalftoneRadius; + uniform float hoverHalftoneWidthShift; uniform float hoverLightStrength; uniform float hoverLightRadius; uniform float hoverFlowStrength; @@ -134,7 +138,6 @@ const halftoneFragmentShader = /* glsl */ ` } void main() { - // Crop to image bounds: discard fragments outside source image (image mode only) if (cropToBounds > 0.5) { vec4 boundsCheck = texture2D(tScene, vUv); if (boundsCheck.a < 0.01) { @@ -164,6 +167,15 @@ const halftoneFragmentShader = /* glsl */ ` hoverLightMask = smoothstep(lightRadiusPx, 0.0, fragDist); } + float hoverHalftoneMask = 0.0; + if ( + abs(hoverHalftonePowerShift) > 0.0001 || + abs(hoverHalftoneWidthShift) > 0.0001 + ) { + float hoverHalftoneRadiusPx = hoverHalftoneRadius * logicalResolution.y; + hoverHalftoneMask = smoothstep(hoverHalftoneRadiusPx, 0.0, fragDist); + } + float hoverFlowMask = 0.0; if (hoverFlowStrength > 0.0) { float hoverRadiusPx = hoverFlowRadius * logicalResolution.y; @@ -191,6 +203,16 @@ const halftoneFragmentShader = /* glsl */ ` vec4 sceneSample = texture2D(tScene, sampleUv); float mask = smoothstep(0.02, 0.08, sceneSample.a); + float localPower = clamp( + s_3 + hoverHalftonePowerShift * hoverHalftoneMask, + -1.5, + 1.5 + ); + float localWidth = clamp( + s_4 + hoverHalftoneWidthShift * hoverHalftoneMask, + 0.05, + 1.4 + ); float lightLift = hoverLightStrength * hoverLightMask * mix(0.78, 1.18, motionBias) * 0.22; float bandRadius = clamp( @@ -199,7 +221,7 @@ const halftoneFragmentShader = /* glsl */ ` sceneSample.r + sceneSample.g + sceneSample.b + - s_3 * length(vec2(0.5)) + localPower * length(vec2(0.5)) ) * (1.0 / 3.0) ) + lightLift, @@ -209,7 +231,7 @@ const halftoneFragmentShader = /* glsl */ ` float alpha = 0.0; if (bandRadius > 0.0001) { - float signedDistance = lineSimpleEt(cellUv, bandRadius, s_4); + float signedDistance = lineSimpleEt(cellUv, bandRadius, localWidth); float edge = 0.02; alpha = (1.0 - smoothstep(0.0, edge, signedDistance)) * mask; } @@ -222,6 +244,360 @@ const halftoneFragmentShader = /* glsl */ ` } `; +const HALFTONE_TRANSMISSION_SHADER_PREFIX = String.raw` +uniform float chromaticAberration; +uniform float anisotropicBlur; +uniform float time; +uniform float distortion; +uniform float distortionScale; +uniform float temporalDistortion; +uniform sampler2D buffer; + +vec3 random3(vec3 c) { + float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0))); + vec3 r; + r.z = fract(512.0 * j); + j *= 0.125; + r.x = fract(512.0 * j); + j *= 0.125; + r.y = fract(512.0 * j); + return r - 0.5; +} + +uint hash(uint x) { + x += (x << 10u); + x ^= (x >> 6u); + x += (x << 3u); + x ^= (x >> 11u); + x += (x << 15u); + return x; +} + +uint hash(uvec2 v) { return hash(v.x ^ hash(v.y)); } +uint hash(uvec3 v) { return hash(v.x ^ hash(v.y) ^ hash(v.z)); } +uint hash(uvec4 v) { + return hash(v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w)); +} + +float floatConstruct(uint m) { + const uint ieeeMantissa = 0x007FFFFFu; + const uint ieeeOne = 0x3F800000u; + m &= ieeeMantissa; + m |= ieeeOne; + float f = uintBitsToFloat(m); + return f - 1.0; +} + +float randomBase(float x) { + return floatConstruct(hash(floatBitsToUint(x))); +} +float randomBase(vec2 v) { + return floatConstruct(hash(floatBitsToUint(v))); +} +float randomBase(vec3 v) { + return floatConstruct(hash(floatBitsToUint(v))); +} +float randomBase(vec4 v) { + return floatConstruct(hash(floatBitsToUint(v))); +} + +float rand(float seed) { + return randomBase(vec3(gl_FragCoord.xy, seed)); +} + +const float F3 = 0.3333333; +const float G3 = 0.1666667; + +float snoise(vec3 p) { + vec3 s = floor(p + dot(p, vec3(F3))); + vec3 x = p - s + dot(s, vec3(G3)); + vec3 e = step(vec3(0.0), x - x.yzx); + vec3 i1 = e * (1.0 - e.zxy); + vec3 i2 = 1.0 - e.zxy * (1.0 - e); + vec3 x1 = x - i1 + G3; + vec3 x2 = x - i2 + 2.0 * G3; + vec3 x3 = x - 1.0 + 3.0 * G3; + vec4 w; + vec4 d; + w.x = dot(x, x); + w.y = dot(x1, x1); + w.z = dot(x2, x2); + w.w = dot(x3, x3); + w = max(0.6 - w, 0.0); + d.x = dot(random3(s), x); + d.y = dot(random3(s + i1), x1); + d.z = dot(random3(s + i2), x2); + d.w = dot(random3(s + 1.0), x3); + w *= w; + w *= w; + d *= w; + return dot(d, vec4(52.0)); +} + +float snoiseFractal(vec3 m) { + return 0.5333333 * snoise(m) + + 0.2666667 * snoise(2.0 * m) + + 0.1333333 * snoise(4.0 * m) + + 0.0666667 * snoise(8.0 * m); +} +`; + +const HALFTONE_TRANSMISSION_PARS_FRAGMENT = String.raw` +#ifdef USE_TRANSMISSION + uniform float _transmission; + uniform float thickness; + uniform float attenuationDistance; + uniform vec3 attenuationColor; + uniform sampler2D refractionEnvMap; + uniform float useEnvMapRefraction; + #ifdef USE_TRANSMISSIONMAP + uniform sampler2D transmissionMap; + #endif + #ifdef USE_THICKNESSMAP + uniform sampler2D thicknessMap; + #endif + uniform vec2 transmissionSamplerSize; + uniform sampler2D transmissionSamplerMap; + uniform mat4 modelMatrix; + uniform mat4 projectionMatrix; + varying vec3 vWorldPosition; + + vec3 getVolumeTransmissionRay( + const in vec3 n, + const in vec3 v, + const in float thicknessValue, + const in float ior, + const in mat4 modelMatrix + ) { + vec3 refractionVector = refract(-v, normalize(n), 1.0 / ior); + vec3 modelScale; + modelScale.x = length(vec3(modelMatrix[0].xyz)); + modelScale.y = length(vec3(modelMatrix[1].xyz)); + modelScale.z = length(vec3(modelMatrix[2].xyz)); + return normalize(refractionVector) * thicknessValue * modelScale; + } + + float applyIorToRoughness( + const in float roughnessValue, + const in float ior + ) { + return roughnessValue * clamp(ior * 2.0 - 2.0, 0.0, 1.0); + } + + vec2 directionToEquirectUv(const in vec3 direction) { + vec3 dir = normalize(direction); + vec2 uv = vec2( + atan(dir.z, dir.x) * 0.15915494309189535 + 0.5, + asin(clamp(dir.y, -1.0, 1.0)) * 0.3183098861837907 + 0.5 + ); + + return vec2(fract(uv.x), 1.0 - clamp(uv.y, 0.0, 1.0)); + } + + vec4 getTransmissionSample( + const in vec2 fragCoord, + const in vec3 transmissionDirection, + const in float roughnessValue, + const in float ior + ) { + if (useEnvMapRefraction > 0.5) { + return texture2D( + refractionEnvMap, + directionToEquirectUv(transmissionDirection) + ); + } + + float framebufferLod = + log2(transmissionSamplerSize.x) * + applyIorToRoughness(roughnessValue, ior); + return texture2D(buffer, fragCoord.xy); + } + + vec3 applyVolumeAttenuation( + const in vec3 radiance, + const in float transmissionDistance, + const in vec3 attenuationColorValue, + const in float attenuationDistanceValue + ) { + if (isinf(attenuationDistanceValue)) { + return radiance; + } + + vec3 attenuationCoefficient = + -log(attenuationColorValue) / attenuationDistanceValue; + vec3 transmittance = + exp(-attenuationCoefficient * transmissionDistance); + + return transmittance * radiance; + } + + vec4 getIBLVolumeRefraction( + const in vec3 n, + const in vec3 v, + const in float roughnessValue, + const in vec3 diffuseColor, + const in vec3 specularColor, + const in float specularF90, + const in vec3 position, + const in mat4 modelMatrix, + const in mat4 viewMatrix, + const in mat4 projMatrix, + const in float ior, + const in float thicknessValue, + const in vec3 attenuationColorValue, + const in float attenuationDistanceValue + ) { + vec3 transmissionRay = getVolumeTransmissionRay( + n, + v, + thicknessValue, + ior, + modelMatrix + ); + vec3 refractedRayExit = position + transmissionRay; + vec4 ndcPos = + projMatrix * viewMatrix * vec4(refractedRayExit, 1.0); + vec2 refractionCoords = ndcPos.xy / ndcPos.w; + refractionCoords += 1.0; + refractionCoords /= 2.0; + vec3 transmissionDirection = normalize(transmissionRay); + vec4 transmittedLight = getTransmissionSample( + refractionCoords, + transmissionDirection, + roughnessValue, + ior + ); + vec3 attenuatedColor = applyVolumeAttenuation( + transmittedLight.rgb, + length(transmissionRay), + attenuationColorValue, + attenuationDistanceValue + ); + vec3 F = EnvironmentBRDF( + n, + v, + specularColor, + specularF90, + roughnessValue + ); + return vec4( + (1.0 - F) * attenuatedColor * diffuseColor, + transmittedLight.a + ); + } +#endif +`; + +const HALFTONE_TRANSMISSION_FRAGMENT_TEMPLATE = String.raw` +material.transmission = _transmission; +material.transmissionAlpha = 1.0; +material.thickness = thickness; +material.attenuationDistance = attenuationDistance; +material.attenuationColor = attenuationColor; +#ifdef USE_TRANSMISSIONMAP + material.transmission *= texture2D(transmissionMap, vUv).r; +#endif +#ifdef USE_THICKNESSMAP + material.thickness *= texture2D(thicknessMap, vUv).g; +#endif + +vec3 pos = vWorldPosition; +float runningSeed = 0.0; +vec3 v = normalize(cameraPosition - pos); +vec3 n = inverseTransformDirection(normal, viewMatrix); +vec3 transmission = vec3(0.0); +float transmissionR; +float transmissionG; +float transmissionB; +float randomCoords = rand(runningSeed++); +float thicknessSmear = + thickness * max(pow(roughnessFactor, 0.33), anisotropicBlur); +vec3 distortionNormal = vec3(0.0); +vec3 temporalOffset = vec3(time, -time, -time) * temporalDistortion; + +if (distortion > 0.0) { + distortionNormal = distortion * vec3( + snoiseFractal(vec3(pos * distortionScale + temporalOffset)), + snoiseFractal(vec3(pos.zxy * distortionScale - temporalOffset)), + snoiseFractal(vec3(pos.yxz * distortionScale + temporalOffset)) + ); +} + +for (float i = 0.0; i < __SAMPLES__.0; i++) { + vec3 sampleNorm = normalize( + n + + roughnessFactor * roughnessFactor * 2.0 * + normalize( + vec3( + rand(runningSeed++) - 0.5, + rand(runningSeed++) - 0.5, + rand(runningSeed++) - 0.5 + ) + ) * + pow(rand(runningSeed++), 0.33) + + distortionNormal + ); + + transmissionR = getIBLVolumeRefraction( + sampleNorm, + v, + material.roughness, + material.diffuseColor, + material.specularColor, + material.specularF90, + pos, + modelMatrix, + viewMatrix, + projectionMatrix, + material.ior, + material.thickness + thicknessSmear * (i + randomCoords) / float(__SAMPLES__), + material.attenuationColor, + material.attenuationDistance + ).r; + + transmissionG = getIBLVolumeRefraction( + sampleNorm, + v, + material.roughness, + material.diffuseColor, + material.specularColor, + material.specularF90, + pos, + modelMatrix, + viewMatrix, + projectionMatrix, + material.ior * (1.0 + chromaticAberration * (i + randomCoords) / float(__SAMPLES__)), + material.thickness + thicknessSmear * (i + randomCoords) / float(__SAMPLES__), + material.attenuationColor, + material.attenuationDistance + ).g; + + transmissionB = getIBLVolumeRefraction( + sampleNorm, + v, + material.roughness, + material.diffuseColor, + material.specularColor, + material.specularF90, + pos, + modelMatrix, + viewMatrix, + projectionMatrix, + material.ior * (1.0 + 2.0 * chromaticAberration * (i + randomCoords) / float(__SAMPLES__)), + material.thickness + thicknessSmear * (i + randomCoords) / float(__SAMPLES__), + material.attenuationColor, + material.attenuationDistance + ).b; + + transmission.r += transmissionR; + transmission.g += transmissionG; + transmission.b += transmissionB; +} + +transmission /= __SAMPLES__.0; +totalDiffuse = mix(totalDiffuse, transmission.rgb, material.transmission); +`; + const GEOMETRY_RUNTIME_SOURCE = String.raw` function makePolarShape(radiusFunction, segments = 320) { const shape = new THREE.Shape(); @@ -843,26 +1219,6 @@ function normalizePreviewDistance(previewDistance: number | undefined) { : REFERENCE_PREVIEW_DISTANCE; } -function toPascalCase(value: string) { - const tokens = value - .replace(/\.[^.]+$/, '') - .replace(/([a-z0-9])([A-Z])/g, '$1 $2') - .replace(/[^a-zA-Z0-9]+/g, ' ') - .trim() - .split(/\s+/) - .filter(Boolean); - - const joined = tokens - .map((token) => token.charAt(0).toUpperCase() + token.slice(1)) - .join(''); - - if (!joined) { - return 'HalftoneDashes'; - } - - return /^[A-Za-z_]/.test(joined) ? joined : `Halftone${joined}`; -} - function extractSerializedJson( content: string, name: string, @@ -995,7 +1351,7 @@ export function deriveExportComponentName( shape?.key ?? 'HalftoneDashes'; - return toPascalCase(source); + return normalizeExportComponentName(source); } function createShapeDescriptor( @@ -1079,6 +1435,364 @@ async function fileToDataUrl( }); } +const GLASS_MATERIAL_RUNTIME_SOURCE = String.raw` +const GLASS_ENVIRONMENT_DATA_URL = ${JSON.stringify(GLASS_ENVIRONMENT_DATA_URL)}; +const GLASS_THICKNESS_TO_WORLD_UNITS = 1 / 320; +const GLASS_ATTENUATION_DISTANCE_MIN = 0.12; +const GLASS_ENVIRONMENT_INTENSITY_BASE = 0.18; +const GLASS_ENVIRONMENT_INTENSITY_MULTIPLIER = 0.12; +const GLASS_ENVIRONMENT_ZOOM = 1.55; +const GLASS_TRANSMISSION_BACKGROUND = new THREE.Color(0x030303); +const MAX_TEXTURE_ANISOTROPY = 8; +const HALFTONE_TRANSMISSION_SHADER_PREFIX = ${JSON.stringify( + HALFTONE_TRANSMISSION_SHADER_PREFIX, +)}; +const HALFTONE_TRANSMISSION_PARS_FRAGMENT = ${JSON.stringify( + HALFTONE_TRANSMISSION_PARS_FRAGMENT, +)}; +const HALFTONE_TRANSMISSION_FRAGMENT_TEMPLATE = ${JSON.stringify( + HALFTONE_TRANSMISSION_FRAGMENT_TEMPLATE, +)}; + +class HalftoneTransmissionMaterial extends THREE.MeshPhysicalMaterial { + constructor(samples = 10) { + super(); + + this.halftoneUniforms = { + chromaticAberration: { value: 0.05 }, + transmission: { value: 0 }, + _transmission: { value: 1 }, + transmissionMap: { value: null }, + refractionEnvMap: { value: null }, + useEnvMapRefraction: { value: 0 }, + roughness: { value: 0 }, + thickness: { value: 0 }, + thicknessMap: { value: null }, + attenuationDistance: { value: Infinity }, + attenuationColor: { value: new THREE.Color('white') }, + anisotropicBlur: { value: 0.1 }, + time: { value: 0 }, + distortion: { value: 0 }, + distortionScale: { value: 0.5 }, + temporalDistortion: { value: 0 }, + buffer: { value: null }, + }; + + this.customProgramCacheKey = () => 'halftone-transmission-' + samples; + + this.onBeforeCompile = (shader) => { + shader.uniforms = { + ...shader.uniforms, + ...this.halftoneUniforms, + }; + shader.defines ??= {}; + + if (this.anisotropy > 0) { + shader.defines.USE_ANISOTROPY = ''; + } + + shader.defines.USE_TRANSMISSION = ''; + shader.fragmentShader = + HALFTONE_TRANSMISSION_SHADER_PREFIX + shader.fragmentShader; + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + HALFTONE_TRANSMISSION_PARS_FRAGMENT, + ); + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + HALFTONE_TRANSMISSION_FRAGMENT_TEMPLATE.replaceAll( + '__SAMPLES__', + String(samples), + ), + ); + }; + + Object.keys(this.halftoneUniforms).forEach((key) => { + Object.defineProperty(this, key, { + configurable: true, + enumerable: true, + get: () => this.halftoneUniforms[key]?.value, + set: (value) => { + this.halftoneUniforms[key].value = value; + }, + }); + }); + } +} + +function setTextureSampling(texture, renderer) { + texture.generateMipmaps = true; + texture.magFilter = THREE.LinearFilter; + texture.minFilter = THREE.LinearMipmapLinearFilter; + texture.anisotropy = Math.min( + renderer.capabilities.getMaxAnisotropy(), + MAX_TEXTURE_ANISOTROPY, + ); +} + +function disposeEnvironmentScene(scene) { + scene.traverse((object) => { + if (object.geometry) { + object.geometry.dispose(); + } + + if (Array.isArray(object.material)) { + object.material.forEach((material) => material.dispose()); + return; + } + + object.material?.dispose?.(); + }); +} + +function createSolidEnvironmentTexture(renderer) { + const pmremGenerator = new THREE.PMREMGenerator(renderer); + const environmentTexture = pmremGenerator.fromScene( + new RoomEnvironment(), + 0.04, + ).texture; + pmremGenerator.dispose(); + + return environmentTexture; +} + +function getTextureImageSize(texture) { + const image = texture.image; + + return { + height: + image?.naturalHeight ?? image?.videoHeight ?? image?.height ?? undefined, + width: + image?.naturalWidth ?? image?.videoWidth ?? image?.width ?? undefined, + }; +} + +function createZoomedGlassTexture(sourceTexture, renderer, zoom) { + if (zoom <= 1) { + return sourceTexture; + } + + const { width, height } = getTextureImageSize(sourceTexture); + + if (!width || !height) { + return sourceTexture; + } + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext('2d'); + + if (!context) { + return sourceTexture; + } + + const cropWidth = width / zoom; + const cropHeight = height / zoom; + const sourceX = (width - cropWidth) / 2; + const sourceY = (height - cropHeight) / 2; + + context.drawImage( + sourceTexture.image, + sourceX, + sourceY, + cropWidth, + cropHeight, + 0, + 0, + width, + height, + ); + + const zoomedTexture = new THREE.CanvasTexture(canvas); + zoomedTexture.colorSpace = sourceTexture.colorSpace; + zoomedTexture.wrapS = THREE.ClampToEdgeWrapping; + zoomedTexture.wrapT = THREE.ClampToEdgeWrapping; + setTextureSampling(zoomedTexture, renderer); + zoomedTexture.needsUpdate = true; + + return zoomedTexture; +} + +function createStudioGlassEnvironmentTexture(renderer, backdropTexture) { + const pmremGenerator = new THREE.PMREMGenerator(renderer); + const environmentTexture = backdropTexture + ? pmremGenerator.fromEquirectangular(backdropTexture).texture + : pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture; + pmremGenerator.dispose(); + + return environmentTexture; +} + +function createFallbackGlassBackdropTexture(renderer) { + const texture = new THREE.DataTexture( + new Uint8Array([3, 3, 3, 255]), + 1, + 1, + THREE.RGBAFormat, + ); + texture.colorSpace = THREE.SRGBColorSpace; + texture.wrapS = THREE.ClampToEdgeWrapping; + texture.wrapT = THREE.ClampToEdgeWrapping; + texture.mapping = THREE.EquirectangularReflectionMapping; + setTextureSampling(texture, renderer); + texture.needsUpdate = true; + + return texture; +} + +function loadTexture(url, renderer, colorSpace) { + const loader = new THREE.TextureLoader(); + + return new Promise((resolve, reject) => { + loader.load( + url, + (texture) => { + texture.colorSpace = colorSpace; + setTextureSampling(texture, renderer); + resolve(texture); + }, + undefined, + reject, + ); + }); +} + +async function loadGlassEnvironmentTexture(renderer) { + const sourceBackgroundTexture = await loadTexture( + GLASS_ENVIRONMENT_DATA_URL, + renderer, + THREE.SRGBColorSpace, + ); + const backgroundTexture = createZoomedGlassTexture( + sourceBackgroundTexture, + renderer, + GLASS_ENVIRONMENT_ZOOM, + ); + if (backgroundTexture !== sourceBackgroundTexture) { + sourceBackgroundTexture.dispose(); + } + backgroundTexture.mapping = THREE.EquirectangularReflectionMapping; + backgroundTexture.wrapS = THREE.ClampToEdgeWrapping; + backgroundTexture.wrapT = THREE.ClampToEdgeWrapping; + backgroundTexture.needsUpdate = true; + const environmentTexture = createStudioGlassEnvironmentTexture( + renderer, + backgroundTexture, + ); + + return { + backgroundTexture, + environmentTexture, + }; +} + +async function createHalftoneMaterialAssets(renderer) { + const solidEnvironmentTexture = createSolidEnvironmentTexture(renderer); + + try { + const glassEnvironmentAssets = await loadGlassEnvironmentTexture(renderer); + + return { + glassBackgroundTexture: glassEnvironmentAssets.backgroundTexture, + glassEnvironmentTexture: glassEnvironmentAssets.environmentTexture, + solidEnvironmentTexture, + }; + } catch { + const fallbackGlassBackdropTexture = + createFallbackGlassBackdropTexture(renderer); + const fallbackGlassEnvironmentTexture = + createStudioGlassEnvironmentTexture(renderer); + + return { + glassBackgroundTexture: fallbackGlassBackdropTexture, + glassEnvironmentTexture: fallbackGlassEnvironmentTexture, + solidEnvironmentTexture, + }; + } +} + +function createHalftoneMaterial() { + return new HalftoneTransmissionMaterial(); +} + +function applyHalftoneMaterialSettings(material, materialSettings, materialAssets) { + const isGlass = materialSettings.surface === 'glass'; + const glassThickness = + materialSettings.thickness * GLASS_THICKNESS_TO_WORLD_UNITS; + const glassEnvironmentIntensity = + GLASS_ENVIRONMENT_INTENSITY_BASE + + materialSettings.environmentPower * GLASS_ENVIRONMENT_INTENSITY_MULTIPLIER; + const glassAttenuationDistance = Math.max( + glassThickness * 4, + GLASS_ATTENUATION_DISTANCE_MIN, + ); + + material.color.set(isGlass ? '#ffffff' : materialSettings.color); + material.roughness = materialSettings.roughness; + material.metalness = materialSettings.metalness; + material.envMap = isGlass + ? materialAssets.glassEnvironmentTexture + : materialAssets.solidEnvironmentTexture; + material.envMapIntensity = isGlass + ? GLASS_ENVIRONMENT_INTENSITY_BASE + + materialSettings.environmentPower * GLASS_ENVIRONMENT_INTENSITY_MULTIPLIER + : 0.25; + material.clearcoat = isGlass ? 1 : 0; + material.clearcoatRoughness = isGlass + ? Math.max(materialSettings.roughness * 0.25, 0.01) + : 0.08; + material.reflectivity = isGlass ? 0.98 : 0.5; + material.transmission = 0; + material._transmission = isGlass ? 1 : 0; + material.refractionEnvMap = isGlass ? materialAssets.glassBackgroundTexture : null; + material.useEnvMapRefraction = isGlass ? 1 : 0; + material.thickness = isGlass ? glassThickness : 0; + material.ior = isGlass ? materialSettings.refraction : 1.5; + material.buffer = null; + material.bumpMap = null; + material.bumpScale = 0; + material.roughnessMap = null; + material.side = THREE.FrontSide; + material.transparent = false; + material.opacity = 1; + material.depthWrite = true; + material.attenuationColor.set(isGlass ? materialSettings.color : 'white'); + material.attenuationDistance = isGlass + ? glassAttenuationDistance + : Infinity; + material.anisotropicBlur = isGlass + ? THREE.MathUtils.lerp(0.03, 0.12, materialSettings.roughness) + : 0.1; + material.chromaticAberration = isGlass ? 0 : 0.05; + material.distortion = 0; + material.distortionScale = 0.5; + material.temporalDistortion = 0; + material.userData.halftoneIsGlass = isGlass; + material.userData.halftoneGlassBacksideThickness = isGlass + ? glassThickness * 2 + : 0; + material.userData.halftoneGlassBacksideEnvIntensity = isGlass + ? glassEnvironmentIntensity * 2.8 + : 0; + material.userData.halftoneUseEnvironmentRefraction = isGlass; + material.envMapIntensity = isGlass ? glassEnvironmentIntensity : 0.25; + + material.needsUpdate = true; +} + +function disposeHalftoneMaterialAssets(materialAssets) { + materialAssets.glassBackgroundTexture.dispose(); + + if (materialAssets.glassEnvironmentTexture !== materialAssets.glassBackgroundTexture) { + materialAssets.glassEnvironmentTexture.dispose(); + } + + materialAssets.solidEnvironmentTexture.dispose(); +} +`; + function serializeRuntimeSource( settings: HalftoneStudioSettings, shape: ExportedShapeDescriptor, @@ -1105,6 +1819,8 @@ ${isImageMode ? '' : GEOMETRY_RUNTIME_SOURCE} ${isImageMode ? '' : IMPORTED_RUNTIME_SOURCE} +${isImageMode ? '' : GLASS_MATERIAL_RUNTIME_SOURCE} + function createRenderTarget(width, height) { return new THREE.WebGLRenderTarget(width, height, { minFilter: THREE.LinearFilter, @@ -1227,12 +1943,7 @@ async function mountHalftoneCanvas(options) { canvas.style.width = '100%'; container.appendChild(canvas); - const pmremGenerator = new THREE.PMREMGenerator(renderer); - const environmentTexture = pmremGenerator.fromScene( - new RoomEnvironment(), - 0.04, - ).texture; - pmremGenerator.dispose(); + const materialAssets = await createHalftoneMaterialAssets(renderer); const scene3d = new THREE.Scene(); scene3d.background = null; @@ -1262,17 +1973,8 @@ async function mountHalftoneCanvas(options) { ); scene3d.add(ambientLight); - const material = new THREE.MeshPhysicalMaterial({ - color: 0xd4d0c8, - roughness: settings.material.roughness, - metalness: settings.material.metalness, - envMap: environmentTexture, - envMapIntensity: 0.25, - clearcoat: 0, - clearcoatRoughness: 0.08, - reflectivity: 0.5, - transmission: 0, - }); + const material = createHalftoneMaterial(); + applyHalftoneMaterialSettings(material, settings.material, materialAssets); const mesh = new THREE.Mesh(geometry, material); scene3d.add(mesh); @@ -1325,6 +2027,9 @@ async function mountHalftoneCanvas(options) { interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, interactionVelocity: { value: new THREE.Vector2(0, 0) }, dragOffset: { value: new THREE.Vector2(0, 0) }, + hoverHalftonePowerShift: { value: 0 }, + hoverHalftoneRadius: { value: 0.2 }, + hoverHalftoneWidthShift: { value: 0 }, hoverLightStrength: { value: 0 }, hoverLightRadius: { value: 0.2 }, hoverFlowStrength: { value: 0 }, @@ -1507,14 +2212,16 @@ async function mountHalftoneCanvas(options) { window.addEventListener('blur', handleWindowBlur); canvas.addEventListener('pointerdown', handlePointerDown); - const clock = new THREE.Clock(); + const clock = new THREE.Timer(); + clock.connect(document); let animationFrameId = 0; - const renderFrame = () => { + const renderFrame = (timestamp) => { animationFrameId = window.requestAnimationFrame(renderFrame); + clock.update(timestamp); const delta = 1 / 60; - const elapsedTime = initialPose.timeElapsed + clock.getElapsedTime(); + const elapsedTime = initialPose.timeElapsed + clock.getElapsed(); halftoneMaterial.uniforms.time.value = elapsedTime; let baseRotationX = 0; @@ -1761,6 +2468,7 @@ async function mountHalftoneCanvas(options) { return () => { window.cancelAnimationFrame(animationFrameId); + clock.dispose(); resizeObserver.disconnect(); canvas.removeEventListener('pointermove', handlePointerMove); canvas.removeEventListener('pointerleave', handlePointerLeave); @@ -1776,7 +2484,7 @@ async function mountHalftoneCanvas(options) { sceneTarget.dispose(); blurTargetA.dispose(); blurTargetB.dispose(); - environmentTexture.dispose(); + disposeHalftoneMaterialAssets(materialAssets); renderer.dispose(); if (canvas.parentNode === container) { @@ -1805,7 +2513,6 @@ async function mountHalftoneCanvas(options) { 1, ); - // Load image const image = await new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); @@ -1894,6 +2601,9 @@ async function mountHalftoneCanvas(options) { interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, interactionVelocity: { value: new THREE.Vector2(0, 0) }, dragOffset: { value: new THREE.Vector2(0, 0) }, + hoverHalftonePowerShift: { value: 0 }, + hoverHalftoneRadius: { value: settings.animation.hoverHalftoneRadius }, + hoverHalftoneWidthShift: { value: 0 }, hoverLightStrength: { value: 0 }, hoverLightRadius: { value: settings.animation.hoverLightRadius }, hoverFlowStrength: { value: 0 }, @@ -2013,8 +2723,8 @@ async function mountHalftoneCanvas(options) { try { canvas.releasePointerCapture(pointerId); - } catch { - // Ignore capture release failures during teardown. + } catch (error) { + void error; } }; @@ -2075,13 +2785,15 @@ async function mountHalftoneCanvas(options) { window.addEventListener('blur', handleWindowBlur); canvas.addEventListener('pointerdown', handlePointerDown); - const clock = new THREE.Clock(); + const clock = new THREE.Timer(); + clock.connect(document); let animationFrameId = 0; - const renderFrame = () => { + const renderFrame = (timestamp) => { animationFrameId = window.requestAnimationFrame(renderFrame); + clock.update(timestamp); - const elapsedTime = clock.getElapsedTime(); + const elapsedTime = clock.getElapsed(); halftoneMaterial.uniforms.time.value = elapsedTime; const pointerActive = interaction.pointerInside; @@ -2101,6 +2813,16 @@ async function mountHalftoneCanvas(options) { -interaction.pointerVelocityY * getVirtualHeight(), ); halftoneMaterial.uniforms.dragOffset.value.set(0, 0); + halftoneMaterial.uniforms.hoverHalftonePowerShift.value = + pointerActive && settings.animation.hoverHalftoneEnabled + ? settings.animation.hoverHalftonePowerShift + : 0; + halftoneMaterial.uniforms.hoverHalftoneRadius.value = + settings.animation.hoverHalftoneRadius; + halftoneMaterial.uniforms.hoverHalftoneWidthShift.value = + pointerActive && settings.animation.hoverHalftoneEnabled + ? settings.animation.hoverHalftoneWidthShift + : 0; halftoneMaterial.uniforms.hoverLightStrength.value = pointerActive && settings.animation.hoverLightEnabled ? settings.animation.hoverLightIntensity @@ -2141,6 +2863,7 @@ async function mountHalftoneCanvas(options) { return () => { window.cancelAnimationFrame(animationFrameId); + clock.dispose(); resizeObserver.disconnect(); canvas.removeEventListener('pointermove', handlePointerMove); canvas.removeEventListener('pointerleave', handlePointerLeave); @@ -2197,6 +2920,8 @@ export function generateReactComponent( modelFilenameOverride, ); const pose = normalizeExportPose(initialPose); + const normalizedComponentName = + normalizeExportComponentName(componentName); const defaultModelUrl = modelFilenameOverride ?? shape.filename ?? 'model.glb'; const defaultImageUrl = imageFilename ?? 'image.png'; @@ -2208,15 +2933,15 @@ ${serializeRuntimeSource(settings, shape, pose, previewDistance)} ${createImageMountScript()} -type ${componentName}Props = { +type ${normalizedComponentName}Props = { imageUrl?: string; style?: CSSProperties; }; -export default function ${componentName}({ +export default function ${normalizedComponentName}({ imageUrl = ${JSON.stringify(`./${defaultImageUrl}`)}, style, -}: ${componentName}Props) { +}: ${normalizedComponentName}Props) { const mountReference = useRef(null); useEffect(() => { @@ -2264,15 +2989,15 @@ ${serializeRuntimeSource(settings, shape, pose, previewDistance)} ${createMountScript()} -type ${componentName}Props = { +type ${normalizedComponentName}Props = { modelUrl?: string; style?: CSSProperties; }; -export default function ${componentName}({ +export default function ${normalizedComponentName}({ modelUrl = ${JSON.stringify(`./${defaultModelUrl}`)}, style, -}: ${componentName}Props) { +}: ${normalizedComponentName}Props) { const mountReference = useRef(null); useEffect(() => { @@ -2329,6 +3054,8 @@ export async function generateStandaloneHtml( modelFilenameOverride, ); const pose = normalizeExportPose(initialPose); + const normalizedComponentName = + normalizeExportComponentName(componentName); const defaultImageUrl = imageFilename ?? 'image.png'; const embeddedImportedModelUrl = !isImageMode && shape.kind === 'imported' && importedFile @@ -2383,7 +3110,7 @@ export async function generateStandaloneHtml( - ${componentName} + ${normalizedComponentName}