import './style.css'; import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { flyChaos } from './fly'; import { randomPng } from './random/png'; const gridSize = 10; const cardSize = 3; const scene = new THREE.Scene(); scene.background = new THREE.Color(0x0f2027); const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 0, 50); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.1; controls.minDistance = 20; controls.maxDistance = 100; const cards: THREE.Mesh[] = []; const geometry = new THREE.BoxGeometry(cardSize, cardSize, 0.1); const textureLoader = new THREE.TextureLoader(); // const pandaTexture = textureLoader.load('./panda.png'); // const pandaTexture = textureLoader.load(randomPng(8)); const pandaTexture = textureLoader.load(randomPng("小熊猫")); pandaTexture.colorSpace = THREE.SRGBColorSpace; const material = new THREE.MeshStandardMaterial({ map: pandaTexture, metalness: 0.2, roughness: 0.8, // 去掉 emissive 让图片更自然 }); for (let x = 0; x < gridSize; x++) { for (let y = 0; y < gridSize; y++) { const card = new THREE.Mesh(geometry, material.clone()); card.position.x = (x - gridSize / 2) * (cardSize + 0.5); card.position.y = (y - gridSize / 2) * (cardSize + 0.5); card.position.z = 0; cards.push(card); scene.add(card); } } const ambientLight = new THREE.AmbientLight(0xffffff, 3.5); scene.add(ambientLight); const pointLight = new THREE.PointLight(0x00ffe7, 1, 200); pointLight.position.set(0, 0, 40); scene.add(pointLight); let flyState = 0; // 0: 原排列, 1: 无序 let flyTimer: number | null = null; let randomTargets: THREE.Vector3[] = []; const originalPositions = cards.map(card => card.position.clone()); function genRandomTargets() { const radius = 20; return cards.map(card => { const theta = Math.random() * Math.PI * 2; const phi = Math.random() * Math.PI; const r = radius * (0.7 + Math.random() * 0.3); return new THREE.Vector3( r * Math.sin(phi) * Math.cos(theta), r * Math.sin(phi) * Math.sin(theta), r * Math.cos(phi) ); }); } function flyTo(targets: THREE.Vector3[], duration: number, cb?: () => void) { const start = performance.now(); const from = cards.map(card => card.position.clone()); function anim() { const t = Math.min((performance.now() - start) / duration, 1); for (let i = 0; i < cards.length; i++) { cards[i].position.lerpVectors(from[i], targets[i], t); } if (t < 1) { requestAnimationFrame(anim); } else { if (cb) cb(); } } requestAnimationFrame(anim); } function loopFly() { if (flyState === 0) { // 飞到无序 randomTargets = genRandomTargets(); flyTo(randomTargets, 2000, () => { flyState = 1; flyTimer = window.setTimeout(loopFly, 2000); }); } else { // 飞回原排列 flyTo(originalPositions, 2000, () => { flyState = 0; flyTimer = window.setTimeout(loopFly, 2000); }); } } loopFly(); function animate() { requestAnimationFrame(animate); scene.rotation.y += 0.005; controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });