import { createSignal, onMount, onCleanup, Show, createEffect, } from "solid-js"; import * as THREE from "three"; import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js"; export interface TokenViewerProps { stlUrl: string | null; } export default function MdTokenViewer(props: TokenViewerProps) { const [viewerRef, setViewerRef] = createSignal(null); const [isLoaded, setIsLoaded] = createSignal(false); const [error, setError] = createSignal(null); let scene: THREE.Scene | null = null; let camera: THREE.PerspectiveCamera | null = null; let renderer: THREE.WebGLRenderer | null = null; let mesh: THREE.Mesh | null = null; let animationId: number | null = null; let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; // 加载 STL 用于预览 const loadSTL = async (url: string) => { const viewerEl = viewerRef(); if (!viewerEl) return; // 清理旧的场景 if (mesh) { scene?.remove(mesh); mesh.geometry.dispose(); (mesh.material as THREE.Material).dispose(); mesh = null; } try { const response = await fetch(url); const stlText = await response.text(); // 解析 STL const loader = new STLLoader(); const geometry = loader.parse(stlText); // 计算边界并居中 geometry.center(); // 创建材质 const material = new THREE.MeshStandardMaterial({ color: 0x808080, metalness: 0.3, roughness: 0.7, side: THREE.DoubleSide, }); mesh = new THREE.Mesh(geometry, material); mesh.rotateX(Math.PI); scene?.add(mesh); // 调整相机 if (camera && scene) { const box = new THREE.Box3().setFromObject(mesh); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); camera.position.set(maxDim * 2, maxDim * 2, maxDim * 2); camera.lookAt(0, 0, 0); } setIsLoaded(true); } catch (e) { console.error("加载 STL 预览失败:", e); setError(e instanceof Error ? e.message : "加载 STL 失败"); } }; // 初始化 Three.js 场景 onMount(() => { const viewerEl = viewerRef(); console.log("Viewer element:", viewerEl); if (!viewerEl) return; // 创建场景 scene = new THREE.Scene(); scene.background = new THREE.Color(0xf3f4f6); // 创建相机 camera = new THREE.PerspectiveCamera( 45, viewerEl.clientWidth / viewerEl.clientHeight, 0.1, 1000 ); camera.position.set(100, 100, 100); // 创建渲染器 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(viewerEl.clientWidth, viewerEl.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); renderer.shadowMap.enabled = true; viewerEl.appendChild(renderer.domElement); // 添加灯光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(50, 100, 50); directionalLight.castShadow = true; scene.add(directionalLight); // 添加坐标轴辅助 const axesHelper = new THREE.AxesHelper(10); scene.add(axesHelper); // 鼠标控制 const handleMouseDown = (e: MouseEvent) => { isDragging = true; previousMousePosition = { x: e.clientX, y: e.clientY }; }; const handleMouseMove = (e: MouseEvent) => { if (!isDragging || !mesh) return; const deltaX = e.clientX - previousMousePosition.x; const deltaY = e.clientY - previousMousePosition.y; mesh.rotation.y += deltaX * 0.01; mesh.rotation.x += deltaY * 0.01; previousMousePosition = { x: e.clientX, y: e.clientY }; }; const handleMouseUp = () => { isDragging = false; }; const handleWheel = (e: WheelEvent) => { if (!camera) return; const zoomSpeed = 0.5; camera.position.multiplyScalar(e.deltaY > 0 ? 1 + zoomSpeed : 1 - zoomSpeed); }; viewerEl.addEventListener("mousedown", handleMouseDown); document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); viewerEl.addEventListener("wheel", handleWheel); // 动画循环 const animate = () => { if (scene && camera && renderer && mesh) { if (!isDragging) { mesh.rotation.y += 0.005; // 自动旋转 } renderer.render(scene, camera); } animationId = requestAnimationFrame(animate); }; animate(); // 清理函数 onCleanup(() => { if (animationId) cancelAnimationFrame(animationId); viewerEl.removeEventListener("mousedown", handleMouseDown); document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); viewerEl.removeEventListener("wheel", handleWheel); if (renderer) { renderer.dispose(); viewerEl.removeChild(renderer.domElement); } if (scene) scene.clear(); }); }); createEffect(() => { if (props.stlUrl) { loadSTL(props.stlUrl); } }); return (
拖动旋转 | 滚动缩放
⚠️ {error()}
加载模型中...
); }