(function() { const dom = document.getElementById('chart-surface-3d'); const width = dom.clientWidth || 800; const height = 500; const scene = new THREE.Scene(); scene.background = new THREE.Color(0x1e293b); const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); camera.position.set(12, 10, 16); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(width, height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); dom.appendChild(renderer.domElement); // 光源 scene.add(new THREE.AmbientLight(0xffffff, 0.5)); const dirLight = new THREE.DirectionalLight(0xffffff, 1); dirLight.position.set(10, 20, 15); scene.add(dirLight); const backLight = new THREE.DirectionalLight(0x64748b, 0.3); backLight.position.set(-10, -5, -10); scene.add(backLight); // 构建曲面网格:Z = sin(X)*cos(Y) * 3 const gridSize = 50, range = 6; const step = 2 * range / gridSize; const vertices = [], indices = [], colors = []; const f = (x, y) => Math.sin(x) * Math.cos(y) * 3; for (let i = 0; i <= gridSize; i++) { for (let j = 0; j <= gridSize; j++) { const x = -range + i * step, y = -range + j * step, z = f(x, y); vertices.push(x, z, y); // 颜色映射:高度 → 冷暖色 const t = (z + 3) / 6; colors.push(0.2 + t * 0.6, 0.3 + t * 0.4, 0.7 - t * 0.5); } } for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { const a = i * (gridSize + 1) + j; const b = a + 1, c = a + (gridSize + 1), d = c + 1; indices.push(a, b, d, a, d, c); } } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); geometry.setIndex(indices); geometry.computeVertexNormals(); const material = new THREE.MeshPhongMaterial({ vertexColors: true, side: THREE.DoubleSide, specular: 0x111111, shininess: 30, transparent: true, opacity: 0.9 }); const surface = new THREE.Mesh(geometry, material); scene.add(surface); // 线框叠加 const wireframe = new THREE.LineSegments( new THREE.WireframeGeometry(geometry), new THREE.LineBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.08 }) ); scene.add(wireframe); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); // 交互 let isDragging = false, prevX = 0, prevY = 0; renderer.domElement.addEventListener('mousedown', e => { isDragging = true; prevX = e.clientX; prevY = e.clientY; }); renderer.domElement.addEventListener('mouseup', () => { isDragging = false; }); renderer.domElement.addEventListener('mousemove', e => { if (!isDragging) return; camera.position.x += (e.clientX - prevX) * 0.05; camera.position.y -= (e.clientY - prevY) * 0.05; camera.lookAt(0, 0, 0); prevX = e.clientX; prevY = e.clientY; }); renderer.domElement.addEventListener('wheel', e => { camera.position.z += e.deltaY * 0.02; camera.position.z = Math.max(6, Math.min(40, camera.position.z)); e.preventDefault(); }); window.addEventListener('resize', () => { const w = dom.clientWidth || 800; camera.aspect = w / height; camera.updateProjectionMatrix(); renderer.setSize(w, height); }); })();