"use client"; import { useEffect, useRef } from "react"; import * as THREE from "three"; import { GLTFLoader } from "three-stdlib"; import { OrbitControls } from "three-stdlib"; import { toast, Toaster } from "sonner"; export default function GLTFViewer() { const mountRef = useRef(null); useEffect(() => { if (!mountRef.current) return; const scene = new THREE.Scene(); scene.background = new THREE.Color(0x0b0b0b); const camera = new THREE.PerspectiveCamera( 75, mountRef.current.clientWidth / mountRef.current.clientHeight, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); mountRef.current.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; scene.add(new THREE.AmbientLight(0xffffff, 1.5)); const dirLight = new THREE.DirectionalLight(0xffffff, 2); dirLight.position.set(5, 10, 5); scene.add(dirLight); const loader = new GLTFLoader(); let mixer: THREE.AnimationMixer | null = null; const wireframeLines: THREE.LineSegments[] = []; loader.load( "/ring.gltf", (gltf) => { const model = gltf.scene; // Center and scale const box = new THREE.Box3().setFromObject(model); const size = box.getSize(new THREE.Vector3()).length(); const center = box.getCenter(new THREE.Vector3()); model.position.sub(center); model.scale.setScalar(2 / size); scene.add(model); model.traverse((child) => { if ((child as THREE.Mesh).isMesh) { const mesh = child as THREE.Mesh; mesh.visible = false; // Use WireframeGeometry (always works) const edgeThreshold = 9; // degrees (try 10–40) const wireGeometry = new THREE.EdgesGeometry( mesh.geometry, THREE.MathUtils.degToRad(edgeThreshold) ); const wireframeLine = new THREE.LineSegments( wireGeometry, new THREE.LineBasicMaterial({ color: 0x00ff00 }) ); wireframeLine.position.copy(mesh.position); wireframeLine.rotation.copy(mesh.rotation); wireframeLine.scale.copy(mesh.scale); wireframeLines.push(wireframeLine); scene.add(wireframeLine); } }); if (gltf.animations.length > 0) { mixer = new THREE.AnimationMixer(model); gltf.animations.forEach((clip) => mixer!.clipAction(clip).play()); } camera.position.set(size / 2, size / 4, size / 2); camera.lookAt(0, 0, 0); }, undefined, (err) => console.error("GLTF LOAD ERROR", err) ); // Click-to-copy only if not dragging const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); const COPY_STRING = "ssh krishna@ayyalasomayajula.net"; let mouseDownPos: { x: number; y: number } | null = null; const DRAG_THRESHOLD = 2; function onMouseDown(event: MouseEvent) { mouseDownPos = { x: event.clientX, y: event.clientY }; } function onClick(event: MouseEvent) { if (!mouseDownPos) return; const dx = event.clientX - mouseDownPos.x; const dy = event.clientY - mouseDownPos.y; if (Math.sqrt(dx * dx + dy * dy) > DRAG_THRESHOLD) return; if (wireframeLines.length === 0) return; const rect = renderer.domElement.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); for (const wf of wireframeLines) { const intersects = raycaster.intersectObject(wf); if (intersects.length > 0) { navigator.clipboard.writeText(COPY_STRING).then(() => { toast.success(`Copied: ${COPY_STRING}`); }); break; } } } renderer.domElement.addEventListener("mousedown", onMouseDown); window.addEventListener("click", onClick); const clock = new THREE.Clock(); const animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); if (mixer) mixer.update(delta); controls.update(); renderer.render(scene, camera); }; animate(); const handleResize = () => { if (!mountRef.current) return; camera.aspect = mountRef.current.clientWidth / mountRef.current.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); window.removeEventListener("click", onClick); renderer.domElement.removeEventListener("mousedown", onMouseDown); mountRef.current?.removeChild(renderer.domElement); }; }, []); return (
); }