无图不欢,先上图
使用方法(以vue3为例)
<template><div class="net" ref="net"></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import NetAnimation from '@/utils/netAnimation.js'let net = ref(null)
onMounted(() => {new NetAnimation({dom: net.value,pointLightsAttr: [{}],axesHelperAttr: {show: true,length: 100},controlAttr: {show: true}})
})</script><style scoped lang="scss">
.net{width: 100%;height: 100%;background-color: #02112e;
}
</style>
netAnimation.js源码
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";class NetAnimation {constructor(opt) {this.dom = opt.domthis.w = nullthis.h = nullthis.netMaxY = opt.netMaxYthis.scene = nullthis.renderer = nullthis.camera = nullthis.cameraAttr = opt.cameraAttrthis.ambientLight = nullthis.ambientLightAttr = opt.ambientLightAttrthis.pointLights = []this.pointLightsAttr = opt.pointLightsAttrthis.axesHelper = nullthis.axesHelperAttr = opt.axesHelperAttrthis.control = nullthis.controlAttr = opt.controlAttrthis.plane = nullthis.planeAttr = opt.planeAttrthis.animationIndex = 0this.requestAnimationFrame = nullthis.init()}init = () => {if(!this.dom){return}this.w = this.dom.clientWidth * (window.devicePixelRatio || 1)this.h = this.dom.clientHeight * (window.devicePixelRatio || 1)// 创建场景this.scene = this.createScene()// 创建renderthis.renderer = this.createWebGLRenderer({dom: this.dom});// 创建相机const cameraAttr = {style: {fov: 45,aspect: this.w / this.h,near: 0.01,far: 10000},position: {x: this.w / 2,y: this.w / 2,z: this.h * 2}}if(this.cameraAttr){this.cameraAttr = Object.assign(cameraAttr, this.cameraAttr)}else{this.cameraAttr = cameraAttr}this.camera = this.createPerspectiveCamera(this.cameraAttr.style)this.camera.position.set(this.cameraAttr.position.x, this.cameraAttr.position.y, this.cameraAttr.position.z);this.camera.lookAt(this.scene.position);// 创建环境光const ambientLightAttr = {show: true,style: {color: "#fff",intensity: 0.1}}if(this.ambientLightAttr){this.ambientLightAttr = Object.assign(ambientLightAttr, this.ambientLightAttr)}else{this.ambientLightAttr = ambientLightAttr}if(this.ambientLightAttr.show){this.ambientLight = this.createAmbientLight(this.ambientLightAttr.style);this.scene.add(this.ambientLight);}// 创建点光源if(!this.netMaxY){this.netMaxY = 60}const pointLightAttr = {style: {color: '#fff',intensity: 1,distance: this.w},position: {x: 0,y: this.netMaxY * 2,z: 0}}if(this.pointLightsAttr?.length){this.pointLightsAttr.forEach(pointLightItem => {pointLightItem = Object.assign(pointLightAttr, pointLightItem)const pointLight = this.createPointLight(pointLightItem.style);pointLight.position.set(pointLightItem.position.x, pointLightItem.position.y, pointLightItem.position.z);this.pointLights.push(pointLight)})this.scene.add(...this.pointLights);}// 创建辅助线const axesHelperAttr = {show: false,length: 100}if(this.axesHelperAttr){this.axesHelperAttr = Object.assign(axesHelperAttr, this.axesHelperAttr)}else{this.axesHelperAttr = axesHelperAttr}if(this.axesHelperAttr.show){this.axesHelper = this.createAxesHelper(this.axesHelperAttr.length)this.scene.add(this.axesHelper);}// 创建轨道控制const controlAttr = {show: false}if(this.controlAttr){this.controlAttr = Object.assign(controlAttr, this.controlAttr)}else{this.controlAttr = controlAttr}if(this.controlAttr.show){this.createControl(this.camera, this.dom);}let planeAttr = {width: this.w,height: this.h,widthSegments: Math.floor(this.w / 20),heightSegments: Math.floor(this.h / 60)}if(this.planeAttr){this.planeAttr = Object.assign(planeAttr, this.planeAttr)}else{this.planeAttr = planeAttr}const geometry = this.createPlaneGeometry(this.planeAttr)const material = this.createMeshPlaneMaterial({color: "#ffffff",wireframe: true,});this.plane = this.createMesh({geometry, materialBasic: material});this.plane.rotation.x = Math.PI * -0.5;// this.plane.rotation.z = 45 * (Math.PI / 180);// this.plane.position.z = 100;this.scene.add( this.plane )// 渲染this.render()}render = () => {//循环调用this.requestAnimationFrame = requestAnimationFrame(this.render);this.animation()this.renderer.render(this.scene, this.camera);}unmount = () => {cancelAnimationFrame(this.requestAnimationFrame)}animation = () => {let animationSpeed = 10let sinXNum = this.planeAttr.widthSegmentslet sinYNum = this.planeAttr.heightSegmentsconst geometry = this.plane.geometryconst att_p = geometry.getAttribute('position');let i = 0;let xi = 0let yi = 0while(i < att_p.count){let x = att_p.getX(i)let y = att_p.getY(i)xi = Math.floor(i / sinXNum)yi = i - xi * sinXNumlet z = (Math.sin(((xi + this.animationIndex / animationSpeed) % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((yi + xi + this.animationIndex / animationSpeed) % sinYNum / sinYNum * Math.PI * 2))) * (this.netMaxY / 2)att_p.setXYZ( i, x, y, z );i += 1;}att_p.needsUpdate = true;geometry.computeVertexNormals();this.animationIndex++this.animationIndex %= sinXNum * sinYNum * animationSpeed}// 以下皆为实体创建方法createScene = () => {return new THREE.Scene();}createPerspectiveCamera = ({ fov, aspect, near, far }) => {// fov — 摄像机视锥体垂直视野角度// aspect — 摄像机视锥体长宽比// near — 摄像机视锥体近端面// far — 摄像机视锥体远端面return new THREE.PerspectiveCamera(fov, aspect, near, far);}createWebGLRenderer = ({ dom, width, height }) => {// renderDom — dom// width — 渲染宽度 一般取domclientWidth// height — 渲染高度 一般取clientHeightif (width === undefined) {width = dom.clientWidth;}if (height === undefined) {height = dom.clientHeight;}const renderer = new THREE.WebGLRenderer();renderer.setPixelRatio(window.devicePixelRatio || 1);renderer.setClearColor('#fff', 0); //设置背景颜色和透明度renderer.setSize(width, height);dom.appendChild(renderer.domElement);return renderer;}createAmbientLight = ({ color, intensity }) => {// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。// intensity - (可选参数) 光照强度。 缺省值 1。return new THREE.AmbientLight(color, intensity);}createPointLight = ({ color, intensity, distance, decay }) => {// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。// intensity - (可选参数) 光照强度。 缺省值 1。// distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.// decay - 沿着光照距离的衰退量。缺省值 2。return new THREE.PointLight(color, intensity, distance, decay);}createPlaneGeometry = ({width, height, widthSegments, heightSegments}) => {return new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);}createMeshPlaneMaterial = (data) => {return new THREE.MeshLambertMaterial(data);}createMesh = ({geometry, materialBasic}) => {return new THREE.Mesh(geometry, materialBasic);}createAxesHelper = (length) => {return new THREE.AxesHelper(length)}createControl = (camera, dom) => {return new OrbitControls(camera, dom);};
}export default NetAnimation
如果电脑性能不错,可以考虑使用以下netAnimation2.js源码
import * as THREE from "three";
import { MeshLine, MeshLineMaterial } from "three.meshline";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";class NetAnimation {constructor(opt) {this.dom = opt.domthis.w = nullthis.h = nullthis.netMaxY = opt.netMaxYthis.scene = nullthis.renderer = nullthis.camera = nullthis.cameraAttr = opt.cameraAttrthis.ambientLight = nullthis.ambientLightAttr = opt.ambientLightAttrthis.pointLights = []this.pointLightsAttr = opt.pointLightsAttrthis.axesHelper = nullthis.axesHelperAttr = opt.axesHelperAttrthis.control = nullthis.controlAttr = opt.controlAttrthis.points = []this.lines = []this.isSameNormalStyle = opt.isSameNormalStylethis.pointAttr = opt.pointAttrthis.lineAttr = opt.lineAttrthis.pointsAttr = []this.linesArrt = []this.animationIndex = 0this.requestAnimationFrame = nullthis.init()}init = () => {if(!this.dom){return}this.w = this.dom.clientWidth * (window.devicePixelRatio || 1)this.h = this.dom.clientHeight * (window.devicePixelRatio || 1)// 创建场景this.scene = this.createScene()// 创建renderthis.renderer = this.createWebGLRenderer({dom: this.dom});// 创建相机const cameraAttr = {style: {fov: 45,aspect: this.w / this.h,near: 0.01,far: 10000},position: {x: this.w / 2,y: this.w / 2,z: this.h * 2}}if(this.cameraAttr){this.cameraAttr = Object.assign(cameraAttr, this.cameraAttr)}else{this.cameraAttr = cameraAttr}this.camera = this.createPerspectiveCamera(this.cameraAttr.style)this.camera.position.set(this.cameraAttr.position.x, this.cameraAttr.position.y, this.cameraAttr.position.z);this.camera.lookAt(this.scene.position);// 创建环境光const ambientLightAttr = {show: true,style: {color: "#fff",intensity: 0.1}}if(this.ambientLightAttr){this.ambientLightAttr = Object.assign(ambientLightAttr, this.ambientLightAttr)}else{this.ambientLightAttr = ambientLightAttr}if(this.ambientLightAttr.show){this.ambientLight = this.createAmbientLight(this.ambientLightAttr.style);// this.scene.add(this.ambientLight);}// 创建点光源if(!this.netMaxY){this.netMaxY = 60}const pointLightAttr = {style: {color: '#fff',intensity: 1,distance: this.w},position: {x: this.netMaxY * 5,y: this.netMaxY * 5,z: this.netMaxY * 5}}if(this.pointLightsAttr?.length){this.pointLightsAttr.forEach(pointLightItem => {pointLightItem = Object.assign(pointLightAttr, pointLightItem)const pointLight = this.createPointLight(pointLightItem.style);pointLight.position.set(pointLightItem.position.x, pointLightItem.position.y, pointLightItem.position.z);this.pointLights.push(pointLight)})this.scene.add(...this.pointLights);}// 创建辅助线const axesHelperAttr = {show: false,length: 100}if(this.axesHelperAttr){this.axesHelperAttr = Object.assign(axesHelperAttr, this.axesHelperAttr)}else{this.axesHelperAttr = axesHelperAttr}if(this.axesHelperAttr.show){this.axesHelper = this.createAxesHelper(this.axesHelperAttr.length)this.scene.add(this.axesHelper);}// 创建轨道控制const controlAttr = {show: false}if(this.controlAttr){this.controlAttr = Object.assign(controlAttr, this.controlAttr)}else{this.controlAttr = controlAttr}if(this.controlAttr.show){this.createControl(this.camera, this.dom);}// 创建点、线console.time('a')this.initPointLineData()if(this.pointsAttr?.length){// 点geometry、material// let pointGeometry = null// let pointMaterial = null// let pointmMesh = null// 线geometry、materiallet lineMaterial = nullif(this.isSameNormalStyle === undefined){this.isSameNormalStyle = true// pointGeometry = this.createSphereGeometry(this.pointAttr.style.normal.geometry);// pointMaterial = this.createMeshLambertMaterial(this.pointAttr.style.normal.material);// pointmMesh = this.createMesh({geometry: pointGeometry, materialBasic: pointMaterial});lineMaterial = this.createMeshLineMaterial(this.lineAttr.style.normal.material);}// this.pointsAttr.forEach(pointAttrItem => {// // 创建点Mesh// let mesh = null// if(!this.isSameNormalStyle){// pointGeometry = this.createSphereGeometry(pointAttrItem.style.normal.geometry);// pointMaterial = this.createMeshLambertMaterial(pointAttrItem.style.normal.material);// mesh = this.createMesh({geometry: pointGeometry, materialBasic: pointMaterial});// }else{// mesh = pointmMesh.clone();// }// mesh.position.set(pointAttrItem.position.x, pointAttrItem.position.y, pointAttrItem.position.z);// this.points.push(mesh)// })this.linesArrt.forEach(lineAttrItem => {// 创建线Meshlet linePositions = []lineAttrItem.forEach(linePoint => {let i = (linePoint.row * this.pointAttr.col) + linePoint.collinePositions.push({...this.pointsAttr[i].position})})const lineGeometry = this.createLineGeometry(linePositions);// if(!this.isSameNormalStyle){// lineMaterial = this.createMeshLineMaterial(lineAttrItem.style.normal.material);// }const lineMesh = this.createMesh({geometry: lineGeometry, materialBasic: lineMaterial});this.lines.push(lineMesh)})// this.scene.add(...this.points);this.scene.add(...this.lines);}console.timeEnd('a')// 渲染this.render()}initPointLineData() {const pointAttr = {width: this.w,height: Math.floor(this.w / 2),row: Math.floor(this.w / 40),col: Math.floor(this.w / 20),// width: this.w * 2,// height: this.w,// row: this.w / 20,// col: this.w / 10,// row: 10,// col: 10,style: {normal: {geometry: {radius: 3,widthSegments: 320,heightSegments: 160,},material: {color: "#ffffff",wireframe: false, //是否将几何体渲染为线框,默认值为false(即渲染为平面多边形)},},light: {geometry: {radius: 1,widthSegments: 320,heightSegments: 160,},material: {color: "#ffffff",wireframe: false,},}}}if(this.pointAttr){this.pointAttr = Object.assign(pointAttr, this.pointAttr)}else{this.pointAttr = pointAttr}const lineAttr = {style: {normal: {material: {color: "#fff",// color: "#3587C7",linewidth: 1,},},light: {material: {color: "#ffffff",linewidth: 1,},}}};if(this.lineAttr){this.lineAttr = Object.assign(lineAttr, this.lineAttr)}else{this.lineAttr = lineAttr}const startX = -this.pointAttr.width / 2const startZ = -this.pointAttr.height / 2const stepX = this.pointAttr.width / this.pointAttr.colconst stepZ = this.pointAttr.height / this.pointAttr.rowconst sinXNum = this.pointAttr.row / 4const sinZNum = this.pointAttr.col / 4for(let i = 0 ; i < this.pointAttr.row; i++){for(let j = 0 ; j < this.pointAttr.col; j++){const x = startX + j * stepXconst z = startZ + i * stepZconst y = (Math.sin((i % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((j + i) % sinZNum / sinZNum * Math.PI * 2))) * (this.netMaxY / 2)this.pointsAttr.push({row: i,col: j,position: {x, y, z},style: {...this.pointAttr.style}})if(!this.linesArrt[i]){this.linesArrt[i] = []}this.linesArrt[i][j] = {row: i,col: j}if(!this.linesArrt[this.pointAttr.row + j]){this.linesArrt[this.pointAttr.row + j] = []}this.linesArrt[this.pointAttr.row + j][i] = {row: i,col: j}}}}render = () => {//循环调用this.requestAnimationFrame = requestAnimationFrame(this.render);this.animation()this.renderer.render(this.scene, this.camera);}unmount = () => {cancelAnimationFrame(this.requestAnimationFrame)}animation = () => {const sinXNum = this.pointAttr.row / 4const sinZNum = this.pointAttr.col / 4const count = this.pointAttr.row * this.pointAttr.colconst animationSpeed = 10 //值越大越慢console.time('b')// if(this.animationIndex % 10 === 0){this.pointsAttr.forEach((pointAttrItem, pointAttrIndex) => {const i = pointAttrItem.rowconst j = pointAttrItem.colpointAttrItem.position.y = (Math.sin(((i + this.animationIndex / animationSpeed) % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((j + i + this.animationIndex / animationSpeed) % sinZNum / sinZNum * Math.PI * 2))) * 30})this.linesArrt.forEach((lineAttrItem, lineAttrIndex) => {let linePositions = []lineAttrItem.forEach(linePoint => {let i = (linePoint.row * this.pointAttr.col) + linePoint.collet point = this.pointsAttr[i]linePositions.push({...point.position})})const lineGeometry = this.createLineGeometry(linePositions);this.updateGeometry(this.lines[lineAttrIndex].geometry, lineGeometry)})// }console.timeEnd('b')this.animationIndex++this.animationIndex %= count * animationSpeed}updateGeometry = (geometry, geometry_source) => {const att_p = geometry.getAttribute('position');const att_ps = geometry_source.getAttribute('position');let i = 0;while(i < att_p.count){att_p.setXYZ( i, att_ps.getX(i), att_ps.getY(i),att_ps.getZ(i) );i += 1;}att_p.needsUpdate = true;geometry.computeVertexNormals();};rand = (n,m) => {var c = m - n + 1;return Math.floor(Math.random() * c + n);} // 以下皆为实体创建方法createScene = () => {return new THREE.Scene();}createPerspectiveCamera = ({ fov, aspect, near, far }) => {// fov — 摄像机视锥体垂直视野角度// aspect — 摄像机视锥体长宽比// near — 摄像机视锥体近端面// far — 摄像机视锥体远端面return new THREE.PerspectiveCamera(fov, aspect, near, far);}createWebGLRenderer = ({ dom, width, height }) => {// renderDom — dom// width — 渲染宽度 一般取domclientWidth// height — 渲染高度 一般取clientHeightif (width === undefined) {width = dom.clientWidth;}if (height === undefined) {height = dom.clientHeight;}const renderer = new THREE.WebGLRenderer();renderer.setPixelRatio(window.devicePixelRatio || 1);renderer.setClearColor('#fff', 0); //设置背景颜色和透明度renderer.setSize(width, height);dom.appendChild(renderer.domElement);return renderer;}createAmbientLight = ({ color, intensity }) => {// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。// intensity - (可选参数) 光照强度。 缺省值 1。return new THREE.AmbientLight(color, intensity);}createPointLight = ({ color, intensity, distance, decay }) => {// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。// intensity - (可选参数) 光照强度。 缺省值 1。// distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.// decay - 沿着光照距离的衰退量。缺省值 2。return new THREE.PointLight(color, intensity, distance, decay);}createSphereGeometry = ({radius,widthSegments,heightSegments,phiStart,phiLength,thetaStart,thetaLength}) => {/*radius — 球体半径,默认为1。widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为32。heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为16。phiStart — 指定水平(经线)起始角度,默认值为0。。phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。thetaStart — 指定垂直(纬线)起始角度,默认值为0。thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。*/return new THREE.SphereGeometry(radius,widthSegments,heightSegments,phiStart,phiLength,thetaStart,thetaLength);}createMeshLambertMaterial = (data) => {return new THREE.MeshLambertMaterial(data);}createLineGeometry = (points) => {// const pointsVector3 = [];// for (let i = 0; i < points.length; i++) {// pointsVector3.push(new THREE.Vector3(points[i].x, points[i].y, points[i].z));// }// const geometry = new THREE.BufferGeometry().setFromPoints(pointsVector3);// const line = new MeshLine();// line.setGeometry(geometry);// return linelet pointsVector3 = []points.forEach(point => {pointsVector3.push(new THREE.Vector3(point.x, point.y, point.z))})let curve = new THREE.CatmullRomCurve3(pointsVector3);var CurvePath = new THREE.CurvePath();// 创建CurvePath对象// CurvePath.curves.push(line1, curve, line2);// 插入多段线条CurvePath.curves.push(curve);return new THREE.TubeGeometry(CurvePath, points.length * 10, 1, 25, false);}createMeshLineMaterial = (data) => {// return new MeshLineMaterial({// lineWidth: data.linewidth,// color: data.color || "white",// dashArray: data.dashArray || 0,// transparent: true,// })return new THREE.MeshLambertMaterial({color: data.color || "white"});}createMesh = ({geometry, materialBasic}) => {return new THREE.Mesh(geometry, materialBasic);}createAxesHelper = (length) => {return new THREE.AxesHelper(length)}createControl = (camera, dom) => {return new OrbitControls(camera, dom);};
}export default NetAnimation