当前位置: 首页 > news >正文

Three.js 简单3d拓扑图教程

简单3d拓扑图 ·3D Topology· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • OrbitControls 相机轨道交互
  • CatmullRomCurve3 样条曲线路径
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示简单3d拓扑图效果:基于 WebGL 实现「简单3d拓扑图」可视化效果,附完整可运行源码;核心用到 OrbitControls、CatmullRomCurve3。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • OrbitControls轨道旋转缩放;开enableDamping时每帧需controls.update()

实现步骤

  • 搭建 Scene / Camera / Renderer 与 OrbitControls
  • rAF 循环中 update 并 render
  • 代码要点

    import * as THREE from "three"

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

    const box = document.getElementById('box')

    const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000) camera.position.set(0, 40, 40)

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true, logarithmicDepthBuffer: true }) renderer.setSize(box.clientWidth, box.clientHeight) renderer.setPixelRatio(window.devicePixelRatio * 2) // 像素比 box.appendChild(renderer.domElement)

    const controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true controls.dampingFactor = 0.05

    window.onresize = () => { renderer.setSize(box.clientWidth, box.clientHeight) camera.aspect = box.clientWidth / box.clientHeight camera.updateProjectionMatrix() }

    animate() function animate() { requestAnimationFrame(animate) controls.update() renderer.render(scene, camera) }

    scene.add(new THREE.AxesHelper(200))

    const arr = [ { id: 1, name: '电脑', color: 0x38c9d8, level: 1, coord: [0, 0], line: [2, 3, 4, 5, 13], icon: HOST + '/files/author/z2586300277.png' }, { id: 2, name: '主机', level: 1, color: 0x3021c1, coord: [2, 1], line: [6, 7, 8, 9, 10, 11], icon: HOST + '/files/author/z2586300277.png' }, { id: 3, name: '显示器', level: 1, color: 0x3021c1, coord: [2, 2], line: [9], icon: HOST + '/files/author/KallkaGo.jpg' }, { id: 4, name: '键盘', level: 1, color: 0x3021c1, coord: [2, -2], line: [6], icon: HOST + '/files/author/z2586300277.png' }, { id: 5, name: '鼠标', level: 1, color: 0x3021c1, coord: [2, 3], line: [], icon: HOST + '/files/author/z2586300277.png' }, { id: 6, name: '主板', level: 1, color: 0xffe0a1, coord: [4, -1], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 7, name: '硬盘', level: 1, color: 0xffe0a1, coord: [4, -2], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 8, name: '显卡', level: 1, color: 0xffe0a1, coord: [4, -3], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 9, name: '屏幕', level: 1, color: 0xffe0a1, coord: [4, 2], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 10, name: 'CPU', level: 1, color: 0xffe0a1, coord: [4, 1], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 11, name: '内存条', level: 1, color: 0xffe0a1, coord: [4, 0], line: [12], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 12, name: '测试', level: 1, color: 'pink', coord: [6, 0], line: [], icon: HOST + '/files/author/flowers-10.jpg' }, { id: 13, name: '测试', level: 2, color: 'pink', coord: [7, 5], line: [], icon: HOST + '/files/author/KallkaGo.jpg' }, ]

    const arr2 = [ [2, 3, 5], [6, 7, 8, 9, 10, 11] ]

    const options = { xzScale: 10, meshScale: 5, flowDirection: 'right', fontSize: 1.5, textHeight: 0 } const { xzScale, meshScale } = options

    // 创建组 const group = new THREE.Group() group.position.set(0, 10, 0) group.rotation.x += Math.PI / 2 scene.add(group);

    // 网格辅助 const grid = new THREE.GridHelper(20 * xzScale, xzScale, '#fff', 0xffffff) group.add(grid) grid.material.opacity = 0.5 grid.material.transparent = true

    // 遍历节点 const boxArr = arr.map((i, k) => { const box = createBoxNode(meshScale, xzScale, i) group.add(box) return box })

    // 创建连接线 boxArr.forEach((i, k) => { const { line } = i.info line.map((id, z) => { const mesh = boxArr.find(j => j.info.id === id) if (mesh) try { const flowLine = createFlowLine(i.position, mesh.position, { ...options, radius: 0.1, color: i.info.color }) group.add(flowLine) } catch (e) { } }) })

    // 创建容器 arr2.forEach((i, k) => { const positions = i.map(j => boxArr.find(z => z.info.id === j).position) const mesh = createContainerNode(positions, meshScale) group.add(mesh) })

    /创建容器/ function createContainerNode(positions, meshScale) { const max = ['x', 'y', 'z'].map(i => Math.max(...positions.map(j => j[i]))) const min = ['x', 'y', 'z'].map(i => Math.min(...positions.map(j => j[i]))) const [width, height, depth] = max.map((i, k) => Math.abs(i - min[k])).map(i => i + meshScale * 2) const geometry = new THREE.BoxGeometry(width, height, depth) const material = new THREE.MeshBasicMaterial({ color: 'red', transparent: true, opacity: 0.2 }) const mesh = new THREE.Mesh(geometry, material) mesh.renderOrder = 1 mesh.position.set((max[0] + min[0]) / 2, (max[1] + min[1]) / 2, (max[2] + min[2]) / 2) return mesh }

    /创建立方体节点/ function createBoxNode(meshScale, xzScale, i) { const { coord } = i const box = new THREE.Mesh( new THREE.BoxGeometry(meshScale, meshScale, meshScale), new THREE.MeshBasicMaterial({ color: i.color || '' }) ) box.position.set(coord[0]xzScale, 0, coord[1]xzScale) box.info = i if (i.icon) { const plane = new THREE.Mesh( new THREE.PlaneGeometry(meshScale0.8, meshScale0.8), new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load(i.icon) }) ) plane.position.set(0, 0, meshScale / 2 * 1.01) const plane2 = plane.clone() plane2.position.set(0, 0, -meshScale / 2 * 1.01) box.add(plane, plane2) }

    box.rotation.x += -Math.PI / 2 return box }

    /创建流程线/ function createFlowLine(p1, p2, options = {}) { const { meshScale, flowDirection } = options let p3 if (flowDirection === 'right') p3 = new THREE.Vector3(p1.x, 0, p2.z) else p3 = new THREE.Vector3(p2.x, 0, p1.z) const distance = p3.distanceTo(p2) const p4 = p3.clone().lerp(p2, (distance - meshScale * 0.8) / distance)

    // 创建曲线 const curve = new THREE.CatmullRomCurve3([p1, p3, p3.clone().multiplyScalar(1.001), p4]) const { radius, segments, color, radialSegments } = options const geometry = new THREE.TubeGeometry(curve, segments || 100, radius || 0.5, radialSegments || 8, false) const material = new THREE.MeshBasicMaterial({ color: color || 0x00ff00 }) const mesh = new THREE.Mesh(geometry, material)

    // 创建一个圆锥箭头 const g = new THREE.ConeGeometry(radius ? radius4 : 2, meshScale0.4, radialSegments || 8) const m = new THREE.MeshBasicMaterial({ color: color || 0x00ff00 }) const arrow = new THREE.Mesh(g, m) arrow.position.copy(p4) const { quaternion } = getDirectionQuaternion(p4, p2) arrow.quaternion.copy(quaternion)

    // 创建一个组 const group = new THREE.Group() group.add(arrow) group.add(mesh)

    return group }

    function getDirectionQuaternion(start, end) { const direction = new THREE.Vector3() direction.subVectors(end, start).normalize() const quaternion = new THREE.Quaternion() quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction) const euler = new THREE.Euler() euler.setFromQuaternion(quaternion) return { quaternion, euler }

    }

    完整源码:GitHub

    小结

    • 本文提供简单3d拓扑图完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.jsqmd.com/news/1090517/

相关文章:

  • 芝麻粒TK版:模块化架构下的蚂蚁森林自动化终极方案
  • Win11Debloat深度解析:Windows系统定制化优化技术方案
  • 如何轻松实现AI智能分层:Layerdivider完整使用教程
  • D3keyHelper终极指南:一键解放双手的暗黑3智能助手
  • Illustrator脚本终极指南:如何用自动化工具提升90%设计效率
  • 无硬件学LVGL:基于Web模拟器+MiroPython速通GUI开发—布局与空间管理篇
  • PCL2启动器性能优化终极指南:彻底解决Minecraft卡顿问题
  • 服务发现——让服务“自动寻址“
  • HS2-HF Patch终极指南:如何通过模块化架构实现Honey Select 2的全面增强
  • 如何用MeEdu快速搭建专属在线网校系统:完整指南
  • 7个技巧让你在Blender中实现机械级精度:CAD_Sketcher参数化设计终极指南
  • 如何5分钟实现STL到STEP格式转换:从网格到实体的专业蜕变指南
  • Blender插件管理终极指南:2000+插件一键掌控的完整解决方案
  • 3个步骤彻底告别XCOM 2模组管理噩梦:AML启动器完整解决方案
  • 终极指南:YgoMaster局域网PvP对战完整教程 - 轻松实现好友联机决斗
  • AFE5805评估板实战指南:从硬件解析到性能测试
  • 3D打印新手必看:BambuStudio终极指南,轻松掌握智能切片与远程控制
  • 082、Flask 进阶:蓝图、上下文栈、g 对象与大规模项目组织
  • 深入解析MSPM0工厂预编程区域:从内存映射寄存器到芯片校准数据实战
  • 大模型记忆容量的物理定律:3.6比特每参数量化原理
  • 从一次端口监听冲突的解决,深入理解127.0.0.1、0.0.0.0与网卡IP的绑定机制
  • Python QQ机器人架构解密:多线程事件驱动模型的技术实现
  • 电影院管理系统(可商用)
  • 从理论到实践:基于同态加密的隐私信息检索方案深度解析
  • 暗黑3技能连点器终极指南:解放双手的智能战斗助手
  • MySQL主从复制报错:UUID冲突导致I/O线程停止的排查与修复
  • 大模型MoE稀疏激活原理与实操:从1.8万亿参数到2%激活的工程真相
  • 第七篇:Handler处理器链,命令到达后经历了什么
  • BurpSuite插件xia_sql:SRC实战中高效检测SQL注入漏洞的利器
  • Windows 11 系统优化终极指南:使用 Win11Debloat 实现专业级性能与隐私保护