diff --git a/apps/www/components/LaunchWeek/13/MetalTicket.tsx b/apps/www/components/LaunchWeek/13/MetalTicket.tsx index 2fd7f4de159..0ed892b3e21 100644 --- a/apps/www/components/LaunchWeek/13/MetalTicket.tsx +++ b/apps/www/components/LaunchWeek/13/MetalTicket.tsx @@ -3,55 +3,49 @@ import React, { useEffect, useRef } from 'react' import * as THREE from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js' import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js' import { useParams } from 'common' const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco Sansalvadore' }) => { - // Will come from auth const params = useParams() - const canvasRef = useRef(null) - // const textLines = ['Sansalvadore', 'Francesco'] - const textLines = params?.username?.split(' ').reverse() || [] + const orbitRef = useRef(null) + const animationFrameRef = useRef() + const targetRotation = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) + const textLines = params?.username?.split(' ').reverse() || username?.split(' ').reverse() || [] const LINE_HEIGHT = 1.4 useEffect(() => { if (!canvasRef.current) return - const isDesktop = window.innerWidth > 768 + const isDesktop = () => window.innerWidth > 768 + const canvasWidth = isDesktop() ? window.innerWidth : window.innerWidth + const canvasHeight = window.innerHeight // Initialize scene, camera, and renderer const scene = new THREE.Scene() - const camera = new THREE.PerspectiveCamera( - 75, - isDesktop - ? window.innerWidth / 2 / window.innerHeight - : window.innerWidth / window.innerHeight, - 0.1, - 1000 - ) + const camera = new THREE.PerspectiveCamera(75, canvasWidth / canvasHeight, 0.1, 1000) const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }) - renderer.setSize(isDesktop ? window.innerWidth / 2 : window.innerWidth, window.innerHeight) + renderer.setSize(canvasWidth, canvasHeight) renderer.shadowMap.enabled = true canvasRef.current.appendChild(renderer.domElement) - // Controls - const controls = new OrbitControls(camera, canvasRef.current) - controls.target.set(0, 0.75, 0) - controls.autoRotate - controls.enableDamping = true - controls.enableZoom = false - controls.dampingFactor = 0.1 - controls.minPolarAngle = Math.PI / 2 - controls.maxPolarAngle = Math.PI / 2 + // Create orbit object for camera rotation + const orbit = new THREE.Object3D() + orbit.rotation.order = 'YXZ' + scene.add(orbit) + orbitRef.current = orbit + // Camera setup + const cameraDistance = 30 + camera.position.z = cameraDistance + orbit.add(camera) + + // Rest of your existing setup code (loader, materials, etc.) const loader = new GLTFLoader() - - // Create a metal material - const metalTexture = new THREE.TextureLoader().load('/images/launchweek/13/ticket/2000px.avif') // Load a metal texture image + const metalTexture = new THREE.TextureLoader().load('/images/launchweek/13/ticket/2000px.avif') const metalMaterial = new THREE.MeshStandardMaterial({ map: metalTexture, color: 0x9ea1a1, @@ -63,34 +57,29 @@ const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco San const rgbeLoader = new RGBELoader() rgbeLoader.load('/images/launchweek/13/ticket/canary_wharf_1k.hdr', (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping - scene.environment = texture - metalMaterial.envMap = texture metalMaterial.envMapIntensity = 7 }) let ticket3DImport: THREE.Mesh - // loader.load('/images/launchweek/13/ticket/3D-ticket-cutout.glb', (gltf) => { - loader.load('/images/launchweek/13/ticket/3D-ticket-cutout-holes.glb', (gltf) => { + loader.load('/images/launchweek/13/ticket/3D-ticket-cutout-no-holes.glb', (gltf) => { ticket3DImport = gltf.scene.children[0] as THREE.Mesh ticket3DImport.rotation.x = Math.PI * 0.5 ticket3DImport.traverse((child) => { if (child) { - child.traverse((node) => { - // @ts-ignore - child.material = metalMaterial - child.receiveShadow = true - }) + // @ts-ignore + child.material = metalMaterial + child.receiveShadow = true } }) scene.add(ticket3DImport) camera.lookAt(ticket3DImport.position) - // Load font and add text geometry as a cut-out + // Load font and add text geometry const fontLoader = new FontLoader() fontLoader.load('/images/launchweek/13/ticket/Inter_Regular.json', (font) => { const textMaterial = new THREE.MeshStandardMaterial({ @@ -109,21 +98,9 @@ const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco San }) const textMesh = new THREE.Mesh(textGeometry, textMaterial) textMesh.updateMatrix() - textMesh.position.set(-5.8, -5 + LINE_HEIGHT * (index + 1), -0.2) // Adjust position as needed + textMesh.position.set(-5.8, -5 + LINE_HEIGHT * (index + 1), -0.2) textMesh.castShadow = true scene.add(textMesh) - - // if (ticket3DImport) { - // ticket3DImport.updateMatrixWorld() - // textMesh.updateMatrixWorld() - // const csgTicket = CSG.fromMesh(ticket3DImport) - // const csgText = CSG.fromMesh(textMesh) - - // const subtractedMesh = CSG.toMesh(csgTicket.subtract(csgText), ticket3DImport.matrix) - // subtractedMesh.material = ticket3DImport.material - // scene.remove(ticket3DImport) // Remove old ticket mesh - // scene.add(subtractedMesh) // Add new ticket with cut-out - // } }) const footerContent = [ @@ -147,7 +124,7 @@ const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco San }) const textMesh = new THREE.Mesh(textGeometry, textMaterial) textMesh.updateMatrix() - textMesh.position.set(line.position.x, line.position.y, line.position.z) // Adjust position as needed + textMesh.position.set(line.position.x, line.position.y, line.position.z) textMesh.castShadow = true scene.add(textMesh) }) @@ -155,93 +132,83 @@ const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco San }) camera.position.z = 30 - const ambientLight = new THREE.AmbientLight(0xffffff, 30) - // scene.add(ambientLight) - // Top right light - const topRightLight = new THREE.SpotLight(0xffffff, 1) - topRightLight.position.set(2, 8, 10) - topRightLight.castShadow = true - // scene.add(topRightLight) - - // Bottom left light - const bottomLeftLight = new THREE.SpotLight(0x44ff4f, 2) - bottomLeftLight.position.set(-5, -5, 10) - bottomLeftLight.target.position.set(0, 0, 0) - bottomLeftLight.castShadow = true - // scene.add(bottomLeftLight) - - // const topRightLightHelper = new THREE.SpotLightHelper(topRightLight) - // const topRightLightCameraHelper = new THREE.CameraHelper(topRightLight.shadow.camera) - // const bottomLeftLightHelper = new THREE.SpotLightHelper(bottomLeftLight) - // const bottomLeftLightCameraHelper = new THREE.CameraHelper(bottomLeftLight.shadow.camera) - - // Helpers - // scene.add(topRightLightHelper) - // scene.add(topRightLightCameraHelper) - // scene.add(bottomLeftLightHelper) - // scene.add(bottomLeftLightCameraHelper) - - // Left light - const leftLight = new THREE.PointLight(0xffffff, 0.015) - leftLight.position.set(50, 0, 100) - leftLight.castShadow = true - // scene.add(leftLight) - - // Right light - const rightLight = new THREE.PointLight(0xffffff, 0.015) - rightLight.position.set(-50, 0, 100) - rightLight.castShadow = true - // scene.add(rightLight) - - // Animation loop + // Animation function with smooth transitions const animate = () => { - requestAnimationFrame(animate) + if (orbitRef.current) { + // Smooth interpolation + orbitRef.current.rotation.x += + (targetRotation.current.x - orbitRef.current.rotation.x) * 0.1 + orbitRef.current.rotation.y += + (targetRotation.current.y - orbitRef.current.rotation.y) * 0.1 + orbitRef.current.rotation.z = 0 + } + renderer.render(scene, camera) + animationFrameRef.current = requestAnimationFrame(animate) } + // Start animation animate() + // Mouse movement handler with distance-based sensitivity + const handleMouseMove = (e: MouseEvent) => { + if (!canvasRef.current || !orbitRef.current) return + + // Get canvas bounds + const canvasRect = canvasRef.current.getBoundingClientRect() + + // Calculate center of the canvas + const centerX = canvasRect.left + canvasRect.width / 2 + const centerY = canvasRect.top + canvasRect.height / 2 + + // Calculate distance from cursor to center + const deltaX = e.clientX - centerX + const deltaY = e.clientY - centerY + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + + // Maximum distance for sensitivity calculation (diagonal of the canvas) + const maxDistance = Math.sqrt( + Math.pow(canvasRect.width / 2, 2) + Math.pow(canvasRect.height / 2, 2) + ) + + // Calculate sensitivity based on distance (inverse relationship) + const sensitivity = 0.002 * (1 - Math.min(distance / maxDistance, 1)) + + // Update target rotation with distance-based sensitivity + targetRotation.current.y = deltaX * sensitivity + targetRotation.current.x = deltaY * sensitivity * 0.3 + } + + // Reset handler with smooth transition + const resetRotation = () => { + targetRotation.current = { x: 0, y: 0 } + console.log('out') + } + // Handle window resize const handleResize = () => { - const isDesktop = window.innerWidth > 768 - camera.aspect = isDesktop - ? window.innerWidth / 2 / window.innerHeight - : window.innerWidth / window.innerHeight + const newWidth = isDesktop() ? window.innerWidth : window.innerWidth + camera.aspect = newWidth / window.innerHeight camera.updateProjectionMatrix() - renderer.setSize(isDesktop ? window.innerWidth / 2 : window.innerWidth, window.innerHeight) + ticket3DImport.position.x = isDesktop() ? 10 : 0 + renderer.setSize(newWidth, window.innerHeight) } - let orbit: THREE.Object3D - - // orbit object on mousemove - const handleMouseMoveRotate = (e: any) => { - let scale = -0.001 - orbit.rotateY((e.movementX - 1) * scale) - orbit.rotateX(e.movementY * scale * 0.1) - orbit.rotation.z = 0 - // TODO: reset initial rotation when mouse leaves the canvas - } - - //the camera rotation pivot - orbit = new THREE.Object3D() - orbit.rotation.order = 'YXZ' //this is important to keep level, so Z should be the last axis to rotate in order... - // orbit.position.copy( ticket.position ); - scene.add(orbit) - - //offset the camera and add it to the pivot - //you could adapt the code so that you can 'zoom' by changing the z value in camera.position in a mousewheel event.. - let cameraDistance = 30 - camera.position.z = cameraDistance - orbit.add(camera) - + // Event listeners window.addEventListener('resize', handleResize) - window.addEventListener('mousemove', handleMouseMoveRotate) + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('mouseout', resetRotation) - // Cleanup on unmount + // Cleanup return () => { window.removeEventListener('resize', handleResize) - window.removeEventListener('mousemove', handleMouseMoveRotate) + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('mouseout', resetRotation) + + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current) + } canvasRef.current?.removeChild(renderer.domElement) renderer.dispose() } @@ -249,7 +216,7 @@ const ThreeCanvas: React.FC<{ username: string }> = ({ username = 'Francesco San return (
-
+
) } diff --git a/apps/www/public/images/launchweek/13/ticket/3D-ticket-cutout-no-holes.glb b/apps/www/public/images/launchweek/13/ticket/3D-ticket-cutout-no-holes.glb new file mode 100644 index 00000000000..05acc40782a Binary files /dev/null and b/apps/www/public/images/launchweek/13/ticket/3D-ticket-cutout-no-holes.glb differ