first commit

This commit is contained in:
2025-12-26 11:55:04 -06:00
commit e54212eee4
29 changed files with 7477 additions and 0 deletions

167
components/GLTFViewer.tsx Normal file
View File

@@ -0,0 +1,167 @@
"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<HTMLDivElement>(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 1040)
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 (
<div ref={mountRef} className="w-full h-full">
<Toaster
theme="dark"
richColors
toastOptions= { { style: { color: "limegreen" } } }
/>
</div>
);
}