
三维露天矿等高线!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0, user-scalableno titleThree.js 矿图CAD等高线可视化系统 - 可交互标记点/title style body { margin: 0; overflow: hidden; font-family: Microsoft YaHei, SimHei, PingFang SC, Helvetica Neue, Arial, sans-serif; } #info { position: absolute; top: 20px; left: 20px; background: rgba(0,0,0,0.85); color: white; padding: 12px 24px; border-radius: 8px; backdrop-filter: blur(10px); pointer-events: none; z-index: 100; border-left: 4px solid #ffaa44; box-shadow: 0 4px 15px rgba(0,0,0,0.3); font-size: 14px; } #info h2 { margin: 0 0 5px 0; font-size: 18px; } #info p { margin: 0; font-size: 12px; opacity: 0.9; } #controls-panel { position: absolute; bottom: 20px; left: 20px; right: 20px; background: rgba(0,0,0,0.75); backdrop-filter: blur(10px); color: white; padding: 12px 20px; border-radius: 8px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 15px; z-index: 100; pointer-events: none; font-size: 12px; border-top: 1px solid rgba(255,255,255,0.2); font-family: monospace; } .stat-item { display: flex; gap: 8px; align-items: baseline; } .stat-label { opacity: 0.7; } .stat-value { color: #ffaa44; font-weight: bold; } .color-bar { display: flex; align-items: center; gap: 8px; } .gradient { width: 150px; height: 12px; background: linear-gradient(to right, #2c5a7a, #4a8c6a, #d4a373, #e9c46a); border-radius: 6px; } media (max-width: 768px) { #controls-panel { font-size: 10px; padding: 8px 12px; } .stat-item { gap: 4px; } } /* 自定义提示框样式 */ .custom-toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, rgba(0,0,0,0.95), rgba(30,30,50,0.95)); backdrop-filter: blur(20px); color: white; padding: 0; border-radius: 16px; z-index: 1000; min-width: 280px; max-width: 400px; animation: slideIn 0.3s ease-out; box-shadow: 0 20px 40px rgba(0,0,0,0.5), 0 0 0 2px rgba(255,170,68,0.3); pointer-events: auto; } keyframes slideIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } keyframes slideOut { from { opacity: 1; transform: translate(-50%, -50%) scale(1); } to { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } } .toast-header { padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.2); display: flex; align-items: center; gap: 12px; } .toast-icon { font-size: 28px; } .toast-title { font-size: 18px; font-weight: bold; flex: 1; } .toast-close { cursor: pointer; font-size: 24px; line-height: 1; color: #aaa; transition: color 0.2s; } .toast-close:hover { color: #fff; } .toast-content { padding: 20px; line-height: 1.6; } .toast-detail { background: rgba(255,255,255,0.1); padding: 12px; border-radius: 8px; margin-top: 12px; font-size: 13px; font-family: monospace; } .toast-footer { padding: 12px 20px; border-top: 1px solid rgba(255,255,255,0.1); text-align: right; font-size: 12px; color: #ffaa44; } .toast-button { background: rgba(255,170,68,0.2); border: 1px solid #ffaa44; color: #ffaa44; padding: 6px 16px; border-radius: 20px; cursor: pointer; font-size: 12px; transition: all 0.2s; } .toast-button:hover { background: rgba(255,170,68,0.4); transform: scale(1.05); } /* 高亮光晕效果 */ .highlight-glow { animation: pulse 0.5s ease-out; } keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255,170,68,0.7); } 100% { box-shadow: 0 0 0 20px rgba(255,170,68,0); } } .error-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.9); color: #ff6666; padding: 20px; border-radius: 12px; z-index: 200; text-align: center; display: none; } /* 提示信息 */ .click-hint { position: absolute; bottom: 80px; right: 20px; background: rgba(0,0,0,0.6); padding: 8px 15px; border-radius: 20px; font-size: 12px; color: #ffaa44; pointer-events: none; z-index: 100; font-family: monospace; } /style /head body div idinfo h2⛏️ 矿山地形等高线系统 | CAD数据可视化/h2 p基于Three.js | 等高距: 20m | 颜色映射: 海拔高度 | 点击标记点查看详情/p /div div idcontrols-panel div classstat-item span classstat-label 等高线数量:/span span classstat-value idcontour-count--/span /div div classstat-item span classstat-label️ 海拔范围:/span span classstat-value idelevation-range--/span /div div classstat-item span classstat-label 总线段数:/span span classstat-value idsegment-count--/span /div div classcolor-bar span classstat-label 低海拔 → 高海拔:/span div classgradient/div /div div classstat-item span classstat-label️ 操作提示:/span span classstat-value鼠标拖拽旋转 | 右键平移 | 滚轮缩放 | 点击标记点/span /div /div div classclick-hint 提示点击主井、副井、观测点查看详细信息 /div div iderror-message classerror-message/div script typeimportmap { imports: { three: https://unpkg.com/three0.128.0/build/three.module.js, three/addons/: https://unpkg.com/three0.128.0/examples/jsm/ } } /script script typemodule import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { CSS2DRenderer, CSS2DObject } from three/addons/renderers/CSS2DRenderer.js; // 错误处理 window.addEventListener(error, (e) { console.error(Runtime error:, e); const errorDiv document.getElementById(error-message); errorDiv.style.display block; errorDiv.innerHTML strong⚠️ 运行时错误/strongbr${e.message}br请查看控制台获取详细信息; setTimeout(() { errorDiv.style.display none; }, 5000); }); // 自定义提示框函数 function showToast(title, content, icon ⛏️, detail null) { // 移除已存在的提示框 const existingToast document.querySelector(.custom-toast); if (existingToast) { existingToast.style.animation slideOut 0.2s ease-out; setTimeout(() { existingToast.remove(); }, 200); } // 创建新提示框 const toast document.createElement(div); toast.className custom-toast; const detailHtml detail ? div classtoast-detail ${detail} /div : ; toast.innerHTML div classtoast-header div classtoast-icon${icon}/div div classtoast-title${title}/div div classtoast-close onclickthis.closest(.custom-toast).remove()✕/div /div div classtoast-content ${content} ${detailHtml} /div div classtoast-footer button classtoast-button onclickthis.closest(.custom-toast).remove()关闭/button /div ; document.body.appendChild(toast); // 3秒后自动关闭 setTimeout(() { if (toast.parentElement) { toast.style.animation slideOut 0.2s ease-out; setTimeout(() toast.remove(), 200); } }, 5000); } // 等待DOM加载完成 document.addEventListener(DOMContentLoaded, () { init(); }); async function init() { try { // --- 初始化场景 --- const scene new THREE.Scene(); scene.background new THREE.Color(0x0a1030); scene.fog new THREE.FogExp2(0x0a1030, 0.0005); // --- 相机 --- const camera new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); camera.position.set(450, 350, 550); camera.lookAt(0, 1400, 0); // --- 渲染器 --- const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); renderer.shadowMap.enabled true; renderer.shadowMap.type THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); // CSS2渲染器 const labelRenderer new CSS2DRenderer(); labelRenderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.domElement.style.position absolute; labelRenderer.domElement.style.top 0px; labelRenderer.domElement.style.left 0px; labelRenderer.domElement.style.pointerEvents none; document.body.appendChild(labelRenderer.domElement); // --- 控制器 --- const controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; controls.dampingFactor 0.05; controls.rotateSpeed 1.0; controls.zoomSpeed 1.2; controls.panSpeed 0.8; controls.screenSpacePanning true; controls.maxPolarAngle Math.PI / 2.2; controls.target.set(0, 1420, 0); // --- Raycaster 用于点击检测 --- const raycaster new THREE.Raycaster(); const mouse new THREE.Vector2(); // 存储可交互对象 const interactableObjects []; // --- 灯光系统 --- const ambientLight new THREE.AmbientLight(0x404060, 0.6); scene.add(ambientLight); const mainLight new THREE.DirectionalLight(0xfff5e0, 1.2); mainLight.position.set(300, 500, 200); mainLight.castShadow true; mainLight.shadow.mapSize.width 1024; mainLight.shadow.mapSize.height 1024; mainLight.shadow.camera.near 0.5; mainLight.shadow.camera.far 800; scene.add(mainLight); const backLight new THREE.DirectionalLight(0x88aaff, 0.5); backLight.position.set(-200, 200, -250); scene.add(backLight); const fillLight new THREE.PointLight(0xccaa77, 0.3); fillLight.position.set(0, 1300, 0); scene.add(fillLight); const accentLight new THREE.PointLight(0xffaa66, 0.4); accentLight.position.set(150, 1480, 120); scene.add(accentLight); // --- 辅助元素: 网格地面 --- const gridHelper new THREE.GridHelper(800, 40, 0x88aacc, 0x335588); gridHelper.position.y 1220; gridHelper.material.transparent true; gridHelper.material.opacity 0.4; scene.add(gridHelper); const ringGeometry new THREE.RingGeometry(280, 320, 32); const ringMaterial new THREE.MeshStandardMaterial({ color: 0x3a6ea5, side: THREE.DoubleSide, transparent: true, opacity: 0.2 }); const ring new THREE.Mesh(ringGeometry, ringMaterial); ring.rotation.x -Math.PI / 2; ring.position.y 1222; scene.add(ring); // --- 生成等高线 --- const elevations [1240, 1260, 1280, 1300, 1320, 1340, 1360, 1380, 1400, 1420, 1440, 1460, 1480, 1500, 1520, 1540, 1560]; const contourLines []; function getColorByElevation(elev) { const minElev 1240; const maxElev 1560; const t (elev - minElev) / (maxElev - minElev); const r Math.min(1, t * 1.5); const g Math.min(1, t * 1.2 0.2); const b Math.max(0, 1 - t * 1.5); return new THREE.Color(r, g, b); } function generateContourPoints(elevation, index) { const points []; const segments 64; const baseRadius 140 index * 12; const twist index * 0.15; const wobble1 18 * Math.sin(index * 0.8); const wobble2 12 * Math.cos(index * 0.5); const offsetX 25 * Math.sin(index * 0.6); const offsetZ 20 * Math.cos(index * 0.7); for (let i 0; i segments; i) { const angle (i / segments) * Math.PI * 2; let radius baseRadius; radius 15 * Math.sin(angle * 3 twist); radius 8 * Math.cos(angle * 5 - twist); radius 5 * Math.sin(angle * 7 index); radius wobble1 * Math.sin(angle * 2); radius wobble2 * Math.cos(angle * 4); radius Math.max(40, radius); let x (radius * Math.cos(angle)) offsetX; let z (radius * Math.sin(angle)) offsetZ; const y elevation 1.5 * Math.sin(angle * 6) 1 * Math.cos(angle * 8); points.push(new THREE.Vector3(x, y, z)); } return points; } const group new THREE.Group(); let totalSegments 0; elevations.forEach((elev, idx) { const mainPoints generateContourPoints(elev, idx); const curve1 new THREE.CatmullRomCurve3(mainPoints); const sampled1 curve1.getPoints(120); const geometry1 new THREE.BufferGeometry().setFromPoints(sampled1); const color1 getColorByElevation(elev); const material1 new THREE.LineBasicMaterial({ color: color1, linewidth: 2 }); const line1 new THREE.Line(geometry1, material1); group.add(line1); contourLines.push(line1); totalSegments sampled1.length; if (idx % 3 0 || idx elevations.length - 1) { const midIdx Math.floor(mainPoints.length / 2); const labelPos mainPoints[midIdx].clone(); labelPos.y 8; const div document.createElement(div); div.textContent ${elev}m; div.style.backgroundColor rgba(20, 20, 40, 0.85); div.style.color #ffdd99; div.style.padding 2px 10px; div.style.borderRadius 20px; div.style.fontSize 13px; div.style.fontWeight bold; div.style.border 2px solid ${color1.getStyle()}; div.style.fontFamily monospace; div.style.backdropFilter blur(4px); div.style.whiteSpace nowrap; const label new CSS2DObject(div); label.position.copy(labelPos); group.add(label); } }); scene.add(group); // --- 创建可点击的标记点 (主井、副井、观测点) --- const mineSites [ { name: 主井, pos: [45, 1445, 65], color: 0xff6666, icon: ⛏️, details: { depth: 568m, output: 1200吨/日, status: 运行中, staff: 45人, equipment: 提升机JK-3.5×2.4 } }, { name: 副井, pos: [-85, 1410, -110], color: 0x66ff66, icon: ️, details: { depth: 412m, output: 人员/材料运输, status: 运行中, staff: 28人, equipment: 罐笼提升系统 } }, { name: 观测点, pos: [-30, 1480, -200], color: 0xff88cc, icon: , details: { elevation: 1480m, equipment: GPS监测站, frequency: 每日4次, data: 沉降监测中, accuracy: ±2mm } } ]; mineSites.forEach(site { // 创建球体标记 const sphereGeo new THREE.SphereGeometry(8, 32, 32); const sphereMat new THREE.MeshStandardMaterial({ color: site.color, emissive: site.color, emissiveIntensity: 0.4, metalness: 0.3, roughness: 0.4 }); const sphere new THREE.Mesh(sphereGeo, sphereMat); sphere.position.set(site.pos[0], site.pos[1], site.pos[2]); sphere.castShadow true; sphere.userData { type: mine_site, name: site.name, icon: site.icon, details: site.details, pos: site.pos }; group.add(sphere); interactableObjects.push(sphere); // 添加光晕效果 (点光源) const glowLight new THREE.PointLight(site.color, 0.5, 30); glowLight.position.set(site.pos[0], site.pos[1], site.pos[2]); group.add(glowLight); sphere.userData.glowLight glowLight; // 添加CSS标签 const div document.createElement(div); div.textContent ${site.icon} ${site.name}; div.style.backgroundColor rgba(0,0,0,0.8); div.style.color #fff; div.style.padding 4px 12px; div.style.borderRadius 20px; div.style.fontSize 13px; div.style.fontWeight bold; div.style.border 2px solid ${new THREE.Color(site.color).getStyle()}; div.style.whiteSpace nowrap; div.style.cursor pointer; div.style.backdropFilter blur(4px); div.style.transition transform 0.2s; const label new CSS2DObject(div); label.position.set(site.pos[0], site.pos[1] 18, site.pos[2]); group.add(label); sphere.userData.label label; }); // 添加选矿厂和废石场作为普通标记 const extraSites [ { name: 选矿厂, pos: [210, 1515, -180], color: 0xffaa66, icon: }, { name: 废石场, pos: [-190, 1375, 210], color: 0x88aaff, icon: } ]; extraSites.forEach(site { const sphereGeo new THREE.SphereGeometry(6, 24, 24); const sphereMat new THREE.MeshStandardMaterial({ color: site.color, emissive: 0x331100, emissiveIntensity: 0.2 }); const sphere new THREE.Mesh(sphereGeo, sphereMat); sphere.position.set(site.pos[0], site.pos[1], site.pos[2]); sphere.castShadow true; group.add(sphere); const div document.createElement(div); div.textContent ${site.icon} ${site.name}; div.style.backgroundColor rgba(0,0,0,0.7); div.style.color #fff; div.style.padding 2px 10px; div.style.borderRadius 16px; div.style.fontSize 11px; div.style.border 1px solid ${new THREE.Color(site.color).getStyle()}; div.style.whiteSpace nowrap; const label new CSS2DObject(div); label.position.set(site.pos[0], site.pos[1] 15, site.pos[2]); group.add(label); }); // --- 创建半透明地形参考面 --- const terrainWidth 580; const terrainDepth 580; const terrainSegments 50; const terrainGeometry new THREE.PlaneGeometry(terrainWidth, terrainDepth, terrainSegments, terrainSegments); terrainGeometry.rotateX(-Math.PI / 2); const positions terrainGeometry.attributes.position.array; for (let i 0; i positions.length; i 3) { const x positions[i]; const z positions[i2]; const dist Math.sqrt(x*x z*z); let y 1240 dist * 0.32; const hill1 75 * Math.exp(-((x-150)**2 (z120)**2) / 8000); const hill2 55 * Math.exp(-((x180)**2 (z-200)**2) / 10000); y hill1 hill2; y 3 * Math.sin(x * 0.03) * Math.cos(z * 0.03); positions[i1] y; } terrainGeometry.computeVertexNormals(); const terrainMaterial new THREE.MeshPhongMaterial({ color: 0x4a7c6e, transparent: true, opacity: 0.25, side: THREE.DoubleSide, emissive: 0x112233 }); const terrainMesh new THREE.Mesh(terrainGeometry, terrainMaterial); terrainMesh.receiveShadow true; scene.add(terrainMesh); // --- 粒子效果 --- const particleCount 800; const particleGeo new THREE.BufferGeometry(); const particlePosArray new Float32Array(particleCount * 3); for (let i 0; i particleCount; i) { particlePosArray[i*3] (Math.random() - 0.5) * 700; particlePosArray[i*31] 1350 Math.random() * 220; particlePosArray[i*32] (Math.random() - 0.5) * 700; } particleGeo.setAttribute(position, new THREE.BufferAttribute(particlePosArray, 3)); const particleMat new THREE.PointsMaterial({ color: 0xaaddff, size: 1.8, transparent: true, opacity: 0.25 }); const particles new THREE.Points(particleGeo, particleMat); scene.add(particles); // --- 点击事件监听 --- window.addEventListener(click, (event) { // 计算鼠标位置归一化坐标 mouse.x (event.clientX / renderer.domElement.clientWidth) * 2 - 1; mouse.y -(event.clientY / renderer.domElement.clientHeight) * 2 1; raycaster.setFromCamera(mouse, camera); const intersects raycaster.intersectObjects(interactableObjects); if (intersects.length 0) { const hit intersects[0].object; const data hit.userData; if (data.type mine_site) { // 高亮效果 hit.material.emissiveIntensity 0.8; setTimeout(() { hit.material.emissiveIntensity 0.4; }, 300); // 光晕闪烁 if (data.glowLight) { data.glowLight.intensity 1.2; setTimeout(() { data.glowLight.intensity 0.5; }, 500); } // 构建详细信息HTML let detailHtml ; if (data.details) { detailHtml strong详细信息/strongbr ${Object.entries(data.details).map(([key, val]) span stylecolor:#ffaa44${key}:/span ${val}br ).join()} ; } // 添加位置信息 detailHtml brspan stylecolor:#ffaa44坐标:/span X:${data.pos[0]}m Y:${data.pos[1]}m Z:${data.pos[2]}m; // 显示提示框 showToast( data.name, 您点击了 strong${data.name}/strongbrbr${detailHtml}, data.icon || , null ); console.log(点击了 ${data.name}, data); } } }); // 鼠标悬停效果 renderer.domElement.style.cursor default; // 更新UI document.getElementById(contour-count).textContent contourLines.length; document.getElementById(elevation-range).textContent 1240m - 1560m; document.getElementById(segment-count).textContent totalSegments.toLocaleString(); // 动画循环 let time 0; function animate() { requestAnimationFrame(animate); time 0.005; particles.rotation.y time * 0.1; accentLight.intensity 0.45 Math.sin(time) * 0.1; controls.update(); renderer.render(scene, camera); labelRenderer.render(scene, camera); } animate(); window.addEventListener(resize, onWindowResize, false); function onWindowResize() { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.setSize(window.innerWidth, window.innerHeight); } console.log(✅ Three.js 等高线系统启动成功可点击标记点); console.log( 生成了 ${contourLines.length} 条等高线${interactableObjects.length} 个可交互标记点); } catch (error) { console.error(初始化失败:, error); const errorDiv document.getElementById(error-message); errorDiv.style.display block; errorDiv.innerHTML strong❌ 系统初始化失败/strongbr${error.message}brbr请检查浏览器控制台获取详细信息; } } /script /body /html