目录

  • 系列文章
  • 前言
  • 新增功能
    • 添加背景
    • 灯光旋转动画
    • 数据入场、出场动画
    • 点击放大
  • 实现效果
  • 实现源码
  • 相关资源

系列文章

three.js实现3d球体树状结构布局——树状结构的实现

前言

本文建议先看系列文章第一篇树状结构的实现,下文内容也将衔接第一篇内容。

新增功能

添加背景

// 球形背景几何体的材质
const createMeshPhongMaterialTexture = (data) => {let materialData = {map: new THREE.TextureLoader().load(data.url)}if(data.side){materialData.side = THREE.BackSide}return new THREE.MeshPhysicalMaterial(materialData);
}const bjMeshStyle = {geometry: {radius: 'auto',widthSegments: 320,heightSegments: 160},material: {url: "./sphere-bg2.jpg",side: true}
}const bjGeometry = createSphereGeometry(bjMeshStyle.geometry);
const bjMaterial = createMeshPhongMaterialTexture(bjMeshStyle.material);
const bjMesh = createMesh(bjGeometry, bjMaterial);
bjMesh.position.set(cameraStyle.lookAt[0],cameraStyle.lookAt[1],cameraStyle.lookAt[2]
);
scene.add(bjMesh);

灯光旋转动画

pointLight = createPointLight({ color: "#fff", intensity: 1 });
let pointLightL = R * 10
pointLight.rotateSpeed =  Math.PI / 100;
pointLight.lightAngle = Math.atan(1);
pointLight.arcR = Math.sqrt(pointLightL * pointLightL * 2);
pointLight.init_position = {x: pointLightL,y: pointLightL,z: pointLightL,
}
pointLight.position.set(pointLightL, pointLightL, pointLightL);
scene.add(pointLight);// 渲染
const render = () => {rotate()//循环调用requestAnimationFrame(render);renderer.render(scene, camera);
};
const resetPointLightPosition = () => {pointLight.position.set(pointLight.init_position.x, pointLight.init_position.y, pointLight.init_position.z);pointLight.lightAngle = Math.atan(1);
}
const rotate = (angle) => {if(pointLight.rotateFlag){return}pointLight.lightAngle += angle || pointLight.rotateSpeedpointLight.lightAngle = pointLight.lightAngle % (Math.PI * 2)pointLight.position.set(Math.sin(pointLight.lightAngle) * pointLight.arcR, pointLight.position.y, Math.cos(pointLight.lightAngle) * pointLight.arcR);
}

数据入场、出场动画

const computedPointPosition = (data,style,ArcRArr,startAngle = 0,endAngle = Math.PI * 2,deep = 0
) => {let totalWight = 0;for (let i = 0; i < data.length; i++) {totalWight += data[i].weight;}let AngleScope = endAngle - startAngle;let curAngle = startAngle;let randTranslate = ArcRArr[deep] * 10 || 1000for (let i = 0; i < data.length; i++) {let item = data[i];let ratioAngle = (item.weight / totalWight) * AngleScope;item.targetPostion = {x: Math.sin(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[0],y: style.startPositionY - deep * (style.yr || 0) + style.centerXYZ[1],z: Math.cos(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[2]}item.translatePostion = {y: rand(-randTranslate, randTranslate),x: rand(-randTranslate, randTranslate),z: rand(-randTranslate, ArcRArr[deep])}if (item?.children?.length) {computedPointPosition(item?.children,style,ArcRArr,curAngle,curAngle + ratioAngle,deep + 1);}curAngle += ratioAngle;}
};const render = () => {rotate()pointAnimation()cameraAnimation()//循环调用requestAnimationFrame(render);renderer.render(scene, camera);
};
const pointAnimation = () => {if(animationIndex === targetAnimationIndex){return}animationIndex++let scale = (targetAnimationIndex - animationIndex) / targetAnimationIndexlet scale2 = animationIndex / targetAnimationIndexsphereGeometrys.forEach((item, index) => {let data = {x: item.originData.targetPostion.x + item.originData.translatePostion.x * scale,y: item.originData.targetPostion.y + item.originData.translatePostion.y * scale,z: item.originData.targetPostion.z + item.originData.translatePostion.z * scale}item.position.set(data.x, data.y, data.z);textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)})old_sphereGeometrys.forEach((item, index) => {let data = {x: item.originData.targetPostion.x + item.originData.translatePostion.x * scale2,y: item.originData.targetPostion.y + item.originData.translatePostion.y * scale2,z: item.originData.targetPostion.z + item.originData.translatePostion.z * scale2}item.position.set(data.x, data.y, data.z);old_textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)})if(animationIndex === targetAnimationIndex){old_sphereGeometrys.forEach((item,index) => {scene.remove(item);scene.remove(old_textGeometrys[index]);})old_sphereGeometrys = []old_textGeometrys = []if(lineGeometrys.length){scene.add(...lineGeometrys);}}
}
const cameraAnimation = () => {if(cameraAnimationIndex === targetCameraAnimationIndex){return}cameraAnimationIndex++let scale = (targetCameraAnimationIndex - cameraAnimationIndex) / targetCameraAnimationIndexcamera.position.set(camera.camera_position.x + camera.translate_camera_position.x * scale,camera.camera_position.y + camera.translate_camera_position.y * scale,camera.camera_position.z + camera.translate_camera_position.z * scale,);camera.lookAt(cameraStyle.lookAt[0],cameraStyle.lookAt[1],cameraStyle.lookAt[2]);
}

点击放大

treeDom.value.addEventListener( 'click', meshOnClick );// 点击回调
const meshOnClick = (event) => {const pointer = new THREE.Vector2();pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;let intersects = [];let raycaster = new THREE.Raycaster();raycaster.setFromCamera( pointer, camera );intersects = raycaster.intersectObjects( sphereGeometrys, true );if ( intersects.length > 0 ) {meshOnClickCb(intersects[0].object)}
}
const meshOnClickCb = (mesh) => {let dx = mesh.position.x - cameraStyle.lookAt[0]let dy = mesh.position.y - cameraStyle.lookAt[1]let dz = mesh.position.z - cameraStyle.lookAt[2]if(Math.abs(dx) < 0.001){dx = 0}if(Math.abs(dy) < 0.001){dy = 0}if(Math.abs(dz) < 0.001){dz = 0}let targetPositon = {}if(dx === 0 && dy === 0 && dz === 0){targetPositon = {x: mesh.position.x + treeStyle.pointInterval,y: mesh.position.y + treeStyle.pointInterval,z: mesh.position.z + treeStyle.pointInterval,}}else if(dx === 0 && dy === 0){let dzPointInterval = dz / Math.abs(dz) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dzPointInterval,y: mesh.position.y + dzPointInterval,z: mesh.position.z + dzPointInterval,}}else if(dy === 0 && dz === 0){let dxPointInterval = dx / Math.abs(dx) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dxPointInterval,y: mesh.position.y + dxPointInterval,z: mesh.position.z + dxPointInterval,}}else if(dx === 0 && dz === 0){let dyPointInterval = dy / Math.abs(dy) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dyPointInterval,y: mesh.position.y + dyPointInterval,z: mesh.position.z + dyPointInterval,}}else{let dd = nullif(dx !== 0){dd = dx}else if(dy !== 0){dd = dy}else if(dz !== 0){dd = dz}let translateL = treeStyle.pointInterval * dd / Math.abs(dd)let dxdd = dx / ddlet dydd = dy / ddlet dzdd = dz / ddlet maxdd = dxdd > dydd ? dxdd : dyddmaxdd = maxdd > dzdd ? maxdd : dzddif(Math.abs(maxdd) > 1){let scale = Math.abs(maxdd) / 1dxdd = dxdd / scaledydd = dydd / scaledzdd = dzdd / scale}targetPositon = {x: mesh.position.x + translateL * dxdd,y: mesh.position.y + translateL * dydd,z: mesh.position.z + translateL * dzdd,}}camera.translate_camera_position = {x: camera.position.x - targetPositon.x,y: camera.position.y - targetPositon.y,z: camera.position.z - targetPositon.z,}camera.camera_position = JSON.parse(JSON.stringify(targetPositon))let length = Math.sqrt(Math.pow(camera.translate_camera_position.x, 2) + Math.pow(camera.translate_camera_position.y, 2) + Math.pow(camera.translate_camera_position.z, 2))cameraAnimationIndex = length > treeStyle.pointInterval ? 0 : Math.floor(length / treeStyle.pointInterval * targetCameraAnimationIndex)
}

实现效果

three.js实现3d球体树状结构布局博客演示视频2

实现源码

<script setup>
import { onMounted, ref, onBeforeUnmount, computed, reactive } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import SpriteText from "three-spritetext";
import { MeshLine, MeshLineMaterial } from "three.meshline";
// 图片
import cameraSvg from '@/assets/images/camera.svg'
import openSvg from '@/assets/images/open.svg'
import closeSvg from '@/assets/images/close.svg'
import startSvg from '@/assets/images/start.svg'
import stopSvg from '@/assets/images/stop.svg'
import dataSvg from '@/assets/images/data.svg'// 渲染dom
let treeDom = ref(null);
// 场景
const createScene = () => {return new THREE.Scene();
};
// 相机(透视投影相机)
const createPerspectiveCamera = ({ fov, aspect, near, far }) => {/* fov — 摄像机视锥体垂直视野角度aspect — 摄像机视锥体长宽比near — 摄像机视锥体近端面far — 摄像机视锥体远端面*/return new THREE.PerspectiveCamera(fov, aspect, near, far);
};
// 渲染器
const createWebGLRenderer = ({ dom, width, height }) => {/* renderDom — domwidth — 渲染宽度 一般取domclientWidthheight — 渲染高度 一般取clientHeight*/if (width === undefined) {width = dom.clientWidth;}if (height === undefined) {height = dom.clientHeight;}const renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);dom.appendChild(renderer.domElement);return renderer;
};
// 辅助线
const createAxesHelper = (length) => {return new THREE.AxesHelper(length);
};
// 环境光
const createAmbientLight = ({ color, intensity }) => {// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。// intensity - (可选参数) 光照强度。 缺省值 1。return new THREE.AmbientLight(color, intensity);
};
// 点光
const createPointLight = ({ color, intensity, distance, decay }) => {/*color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。intensity - (可选参数) 光照强度。 缺省值 1。distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.decay - 沿着光照距离的衰退量。缺省值 2。*/return new THREE.PointLight(color, intensity, distance, decay);
};
// 球形几何体
const 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);
};
// 球形几何体的材质
const createMeshLambertMaterial = (data) => {return new THREE.MeshLambertMaterial(data);
};
// 球形背景几何体的材质
const createMeshPhongMaterialTexture = (data) => {let materialData = {map: new THREE.TextureLoader().load(data.url)}if(data.side){materialData.side = THREE.BackSide}return new THREE.MeshPhysicalMaterial(materialData);
}
// 线几何体
const 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 line;
};
// 线几何体的材质
const createMeshLineMaterial = (data) => {return new MeshLineMaterial({lineWidth: data.linewidth,color: data.color || "white",dashArray: data.dashArray || 0,transparent: true,});
};
// 物体
const createMesh = (geometry, materialBasic) => {return new THREE.Mesh(geometry, materialBasic);
};
// 文本
const createText = ({ text, size, color }) => {let textClass = new SpriteText(text, size);textClass.color = color;return textClass;
};
// 轨道控制
const createControl = (camera, dom) => {return new OrbitControls(camera, dom);
};// 3d树布局算法 灵感来源 梯形/三角形转换为圆锥
const computedDataWeight = (data) => {let weight = 0;for (let i = 0; i < data.length; i++) {let item = data[i];if (item?.children?.length) {item.weight = computedDataWeight(item.children);weight += item.weight;} else {item.weight = 1;weight += 1;}}return weight;
};
const computedArcRArr = (data, pointInterval) => {let ArcRArr = [];let ArcData = [];let ArcWeight = [];formatTreeToArcData(data, ArcData);for (let i = 0; i < ArcData.length; i++) {let item = ArcData[i];let weight = 0;for (let j = 0; j < item.length; j++) {weight += item[j].weight;}ArcWeight.push(weight);}let R = computedArcR(pointInterval, ArcWeight[0]);// 半径算法for (let i = 0; i < ArcData.length; i++) {let item = ArcData[i];if (ArcWeight[i] < ArcWeight[0]) {// 不是完全层ArcRArr.push(R);} else {if (item.length > 1) {// 完全层let DValue = 0;item.forEach((weight) => {DValue += Math.floor(weight.weight / 2);});ArcRArr.push(((ArcWeight[i] - DValue) / ArcWeight[i]) * R);} else {ArcRArr.push(0);}}}return { ArcRArr, R };
};
const formatTreeToArcData = (data, ArcData, deep = 0) => {data.forEach((element) => {if (!ArcData[deep]) {ArcData[deep] = [];}ArcData[deep].push({label: element.label,point_uuid: element.point_uuid,weight: element.weight,});if (element?.children?.length) {formatTreeToArcData(element?.children, ArcData, deep + 1);}});
};
const computedArcR = (pointInterval, points) => {if (points === 1) {return pointInterval * 2;}let arcR =pointInterval /2 /Math.cos((Math.PI / 180) * (((points - 2) * 180) / points / 2));if (arcR < pointInterval) {arcR = pointInterval * 2;}return arcR;
};
const computedTreeStyleAuto = (style, ArcRArr, R) => {if (style.yr === "auto") {style.yr = ArcRArr.length === 1 ? R : R / (ArcRArr.length - 1);}style.startPositionY =((ArcRArr.length - 1) / 2) * style.yr + style.centerXYZ[1];
};
const computedPointPosition = (data,style,ArcRArr,startAngle = 0,endAngle = Math.PI * 2,deep = 0
) => {let totalWight = 0;for (let i = 0; i < data.length; i++) {totalWight += data[i].weight;}let AngleScope = endAngle - startAngle;let curAngle = startAngle;let randTranslate = ArcRArr[deep] * 10 || 1000for (let i = 0; i < data.length; i++) {let item = data[i];let ratioAngle = (item.weight / totalWight) * AngleScope;item.targetPostion = {x: Math.sin(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[0],y: style.startPositionY - deep * (style.yr || 0) + style.centerXYZ[1],z: Math.cos(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[2]}item.translatePostion = {y: rand(-randTranslate, randTranslate),x: rand(-randTranslate, randTranslate),z: rand(-randTranslate, ArcRArr[deep])}if (item?.children?.length) {computedPointPosition(item?.children,style,ArcRArr,curAngle,curAngle + ratioAngle,deep + 1);}curAngle += ratioAngle;}
};// 计算camera初始位置
const computedCameraStyle = (style, dom, treeStyle, R) => {if (style.position === "auto") {style.position = {x: 0,y: treeStyle.yr * 1.5,z: R * 3,};}if (style.data === "auto") {style.data = {fov: 45,aspect: dom.clientWidth / dom.clientHeight,near: 0.1,far: R * 1000,};}if (style.lookAt === "auto") {style.lookAt = JSON.parse(JSON.stringify(treeStyle.centerXYZ));}
};// 计算
const computedBjMeshStyleAuto = (style, R) => {style.geometry.radius = R
}// 随机数
const rand = (n, m) => {const c = m - n + 1return Math.floor(Math.random() * c + n)
}// randTree
const randTree = (deep = 0, maxDeep = 5, parentName) => {let tree = []let length = rand(1,3)for(let i = 0; i < length; i++){let data = {name: parentName ? parentName + '-' + i : i + ''}if(rand(0,2) && deep < maxDeep){data.children = randTree(deep + 1, maxDeep, data.name)}tree.push(data)}return tree
}const originTreeStyle = {centerXYZ: [0, 0, 0],yr: "auto",pointInterval: 10,
};
let treeStyle = null
const originCameraStyle = {position: "auto",data: "auto",lookAt: "auto",
};
let cameraStyle = null
const bjMeshStyle = {geometry: {radius: 'auto',widthSegments: 320,heightSegments: 160},material: {url: "./sphere-bg2.jpg",side: true}
}
const sphereMeshStyle = {geometry: {radius: 1,widthSegments: 320,heightSegments: 160,},material: {color: "#ffffff",wireframe: false, //是否将几何体渲染为线框,默认值为false(即渲染为平面多边形)},
};
const lineMeshStyle = {material: {color: "#ffffff",linewidth: 0.2,},
};
const textMeshStyle = {material: {size: 0.5,color: "#ffffff",},
};
let scene = null;
let camera = null;
let renderer = null;
let control = null;
let bjMesh = null;
let axes = null;
let ambientLight = null;
let pointLight = null;
let animationIndex = 0
let targetAnimationIndex = 50
let sphereGeometrys = [];
let textGeometrys = [];
let lineGeometrys = [];
let old_sphereGeometrys = [];
let old_textGeometrys = [];
let cameraAnimationIndex = 0
let targetCameraAnimationIndex = 50const init = (rendererDom, data) => {treeStyle = JSON.parse(JSON.stringify(originTreeStyle))cameraStyle = JSON.parse(JSON.stringify(originCameraStyle))computedDataWeight(data);let { ArcRArr, R } = computedArcRArr(data, treeStyle.pointInterval);computedTreeStyleAuto(treeStyle, ArcRArr, R);computedPointPosition(data, treeStyle, ArcRArr);computedCameraStyle(cameraStyle, rendererDom, treeStyle, R);if(!scene){scene = createScene();}if(!camera){camera = createPerspectiveCamera(cameraStyle.data);camera.position.set(cameraStyle.position.x,cameraStyle.position.y,cameraStyle.position.z);}camera.translate_camera_position = {x: camera.position.x - cameraStyle.position.x,y: camera.position.y - cameraStyle.position.y,z: camera.position.z - cameraStyle.position.z,}camera.camera_position = cameraStyle.positioncamera.init_camera_position = JSON.parse(JSON.stringify(cameraStyle.position))if(!renderer){renderer = createWebGLRenderer({dom: rendererDom,});}if(!control){control = createControl(camera, rendererDom);}if(!bjMesh){computedBjMeshStyleAuto(bjMeshStyle, R * 12);const bjGeometry = createSphereGeometry(bjMeshStyle.geometry);const bjMaterial = createMeshPhongMaterialTexture(bjMeshStyle.material);bjMesh = createMesh(bjGeometry, bjMaterial);bjMesh.position.set(cameraStyle.lookAt[0],cameraStyle.lookAt[1],cameraStyle.lookAt[2]);scene.add(bjMesh);}if(!axes){axes = createAxesHelper(R);scene.add(axes);}if(!ambientLight){ambientLight = createAmbientLight({ color: "#fff", intensity: 0.2 });scene.add(ambientLight);}if(!pointLight){pointLight = createPointLight({ color: "#fff", intensity: 1 });let pointLightL = R * 10pointLight.rotateSpeed =  Math.PI / 100;pointLight.lightAngle = Math.atan(1);pointLight.arcR = Math.sqrt(pointLightL * pointLightL * 2);pointLight.init_position = {x: pointLightL,y: pointLightL,z: pointLightL,}pointLight.position.set(pointLightL, pointLightL, pointLightL);scene.add(pointLight);}animationIndex = 0cameraAnimationIndex = 0old_sphereGeometrys = [...sphereGeometrys];old_textGeometrys = [...textGeometrys];lineGeometrys.forEach(item => {scene.remove(item);})sphereGeometrys = [];textGeometrys = [];lineGeometrys = [];initGeometrys(data, sphereGeometrys, textGeometrys, lineGeometrys);scene.add(...sphereGeometrys);scene.add(...textGeometrys);render();
};
const initGeometrys = (data, sphereGeometrys, textGeometrys, lineGeometrys, parentPosition) => {for (let i = 0; i < data.length; i++) {let item = data[i];const geometry = createSphereGeometry(sphereMeshStyle.geometry);const material = createMeshLambertMaterial(sphereMeshStyle.material);const mesh = createMesh(geometry, material);mesh.position.set(item.targetPostion.x + item.translatePostion.x, item.targetPostion.y + item.translatePostion.y, item.targetPostion.z + item.translatePostion.z);mesh.originData = itemsphereGeometrys.push(mesh);geometry.dispose();material.dispose();const text = createText({text: item.name,size: textMeshStyle.material.size,color: textMeshStyle.material.color,});text.position.x = item.targetPostion.x + item.translatePostion.x;text.position.y = item.targetPostion.y + item.translatePostion.y + sphereMeshStyle.geometry.radius * 2;text.position.z = item.targetPostion.z + item.translatePostion.z;textGeometrys.push(text);if (parentPosition) {const lineGeometry = createLineGeometry([parentPosition,{ x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z },]);const lineMaterial = createMeshLineMaterial(lineMeshStyle.material);const lineMesh = createMesh(lineGeometry, lineMaterial);lineGeometrys.push(lineMesh);lineGeometry.dispose();lineMaterial.dispose();}if (item?.children?.length) {initGeometrys(item.children,sphereGeometrys,textGeometrys,lineGeometrys,{ x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z });}}
};
// 渲染
const render = () => {rotate()pointAnimation()cameraAnimation()//循环调用requestAnimationFrame(render);renderer.render(scene, camera);
};
const resetPointLightPosition = () => {pointLight.position.set(pointLight.init_position.x, pointLight.init_position.y, pointLight.init_position.z);pointLight.lightAngle = Math.atan(1);
}
const rotate = (angle) => {if(pointLight.rotateFlag){return}pointLight.lightAngle += angle || pointLight.rotateSpeedpointLight.lightAngle = pointLight.lightAngle % (Math.PI * 2)pointLight.position.set(Math.sin(pointLight.lightAngle) * pointLight.arcR, pointLight.position.y, Math.cos(pointLight.lightAngle) * pointLight.arcR);
}
const pointAnimation = () => {if(animationIndex === targetAnimationIndex){return}animationIndex++let scale = (targetAnimationIndex - animationIndex) / targetAnimationIndexlet scale2 = animationIndex / targetAnimationIndexsphereGeometrys.forEach((item, index) => {let data = {x: item.originData.targetPostion.x + item.originData.translatePostion.x * scale,y: item.originData.targetPostion.y + item.originData.translatePostion.y * scale,z: item.originData.targetPostion.z + item.originData.translatePostion.z * scale}item.position.set(data.x, data.y, data.z);textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)})old_sphereGeometrys.forEach((item, index) => {let data = {x: item.originData.targetPostion.x + item.originData.translatePostion.x * scale2,y: item.originData.targetPostion.y + item.originData.translatePostion.y * scale2,z: item.originData.targetPostion.z + item.originData.translatePostion.z * scale2}item.position.set(data.x, data.y, data.z);old_textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)})if(animationIndex === targetAnimationIndex){old_sphereGeometrys.forEach((item,index) => {scene.remove(item);scene.remove(old_textGeometrys[index]);})old_sphereGeometrys = []old_textGeometrys = []if(lineGeometrys.length){scene.add(...lineGeometrys);}}
}
const cameraAnimation = () => {if(cameraAnimationIndex === targetCameraAnimationIndex){return}cameraAnimationIndex++let scale = (targetCameraAnimationIndex - cameraAnimationIndex) / targetCameraAnimationIndexcamera.position.set(camera.camera_position.x + camera.translate_camera_position.x * scale,camera.camera_position.y + camera.translate_camera_position.y * scale,camera.camera_position.z + camera.translate_camera_position.z * scale,);camera.lookAt(cameraStyle.lookAt[0],cameraStyle.lookAt[1],cameraStyle.lookAt[2]);
}
// 点击回调
const meshOnClick = (event) => {const pointer = new THREE.Vector2();pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;let intersects = [];let raycaster = new THREE.Raycaster();raycaster.setFromCamera( pointer, camera );intersects = raycaster.intersectObjects( sphereGeometrys, true );if ( intersects.length > 0 ) {meshOnClickCb(intersects[0].object)}
}
const meshOnClickCb = (mesh) => {let dx = mesh.position.x - cameraStyle.lookAt[0]let dy = mesh.position.y - cameraStyle.lookAt[1]let dz = mesh.position.z - cameraStyle.lookAt[2]if(Math.abs(dx) < 0.001){dx = 0}if(Math.abs(dy) < 0.001){dy = 0}if(Math.abs(dz) < 0.001){dz = 0}let targetPositon = {}if(dx === 0 && dy === 0 && dz === 0){targetPositon = {x: mesh.position.x + treeStyle.pointInterval,y: mesh.position.y + treeStyle.pointInterval,z: mesh.position.z + treeStyle.pointInterval,}}else if(dx === 0 && dy === 0){let dzPointInterval = dz / Math.abs(dz) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dzPointInterval,y: mesh.position.y + dzPointInterval,z: mesh.position.z + dzPointInterval,}}else if(dy === 0 && dz === 0){let dxPointInterval = dx / Math.abs(dx) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dxPointInterval,y: mesh.position.y + dxPointInterval,z: mesh.position.z + dxPointInterval,}}else if(dx === 0 && dz === 0){let dyPointInterval = dy / Math.abs(dy) * treeStyle.pointInterval * 2targetPositon = {x: mesh.position.x + dyPointInterval,y: mesh.position.y + dyPointInterval,z: mesh.position.z + dyPointInterval,}}else{let dd = nullif(dx !== 0){dd = dx}else if(dy !== 0){dd = dy}else if(dz !== 0){dd = dz}let translateL = treeStyle.pointInterval * dd / Math.abs(dd)let dxdd = dx / ddlet dydd = dy / ddlet dzdd = dz / ddlet maxdd = dxdd > dydd ? dxdd : dyddmaxdd = maxdd > dzdd ? maxdd : dzddif(Math.abs(maxdd) > 1){let scale = Math.abs(maxdd) / 1dxdd = dxdd / scaledydd = dydd / scaledzdd = dzdd / scale}targetPositon = {x: mesh.position.x + translateL * dxdd,y: mesh.position.y + translateL * dydd,z: mesh.position.z + translateL * dzdd,}}camera.translate_camera_position = {x: camera.position.x - targetPositon.x,y: camera.position.y - targetPositon.y,z: camera.position.z - targetPositon.z,}camera.camera_position = JSON.parse(JSON.stringify(targetPositon))let length = Math.sqrt(Math.pow(camera.translate_camera_position.x, 2) + Math.pow(camera.translate_camera_position.y, 2) + Math.pow(camera.translate_camera_position.z, 2))cameraAnimationIndex = length > treeStyle.pointInterval ? 0 : Math.floor(length / treeStyle.pointInterval * targetCameraAnimationIndex)
}// 操作
let openOperateFlag = ref(true)
const changeOpenOperateFlag = () => {openOperateFlag.value = !openOperateFlag.value
}
const resetCameraPosition = () => {camera.translate_camera_position = {x: camera.position.x - camera.init_camera_position.x,y: camera.position.y - camera.init_camera_position.y,z: camera.position.z - camera.init_camera_position.z,}camera.camera_position = JSON.parse(JSON.stringify(camera.init_camera_position))let length = Math.sqrt(Math.pow(camera.translate_camera_position.x, 2) + Math.pow(camera.translate_camera_position.y, 2) + Math.pow(camera.translate_camera_position.z, 2))cameraAnimationIndex = length > treeStyle.pointInterval ? 0 : Math.floor(length / treeStyle.pointInterval * targetCameraAnimationIndex)
}
let pointLightFlag = ref(false)
const changePointLightFlag = () => {pointLight.rotateFlag = !pointLight.rotateFlagresetPointLightPosition()pointLightFlag.value = pointLight.rotateFlag
}
const changeData = () => {let data = randTree();let rendererDom = treeDom.value;init(rendererDom, data);
}onMounted(() => {changeData()treeDom.value.addEventListener( 'click', meshOnClick );
});
</script><template><div class="tree-new-page"><div class="operate"><div class="open-operate frosted-glass"  @click="changeOpenOperateFlag"><img :src="openOperateFlag ? closeSvg : openSvg"/></div><div :class="{'operate-list': true, 'frosted-glass': true, 'close': !openOperateFlag}"><div class="operate-item camera" @click="resetCameraPosition"><img :src="cameraSvg"/><div class="label">返回正视角</div></div><div class="operate-item point-light" @click="changePointLightFlag"><img :src="pointLightFlag ? stopSvg : startSvg"/><div class="label">{{pointLightFlag ? '停止灯光旋转' : '开启灯光旋转'}}</div></div><div class="operate-item data" @click="changeData"><img :src="dataSvg"/><div class="label">更换数据</div></div></div></div><div class="tree-new" ref="treeDom"></div></div>
</template><style scoped lang="scss">
.tree-new-page {width: 100%;height: 100%;overflow: hidden;background-color: #000;position: relative;.tree-new {width: 100%;height: 100%;}.operate{position: absolute;z-index: 10;left: 0;top: 50%;transform: translateY(-50%);.open-operate{width: 22px;position: relative;padding: 6px 10px;margin-bottom: 10px;display: flex;align-items: center;cursor: pointer;&.frosted-glass{border-radius: 0 4px 4px 0;}img{height: 20px;margin-left: 2px;}}.operate-list{position: relative;padding: 10px;&.frosted-glass{border-radius: 0 4px 4px 0;}&.close{.operate-item{.label{margin-left: 0px;width: 0px;}}}.operate-item{display: flex;align-items: center;justify-content: space-between;margin-bottom: 10px;cursor: pointer;&:last-child{margin-bottom: 0px;}img{height: 20px;}.label{width: 100px;white-space: nowrap;overflow: hidden;color: $defaultWhite;font-size: 16px;margin-left: 10px;transition: all .3s;}}.point-light{img{height: 24px;margin-left: -2px;}}}}.frosted-glass {&::after {position: absolute;top: 0;left: 0;z-index: -1;content: "";display: block;width: 100%;height: 100%;background-color: rgba(255, 255, 255, 0.1);backdrop-filter: blur(8px);box-shadow: 0 0 4px rgb(93, 93, 93);border-radius: 4px;}}
}
</style>

相关资源

下载地址:点击此处,如果打不开则在审核中,可以先搜藏博客,后面再来下载。
项目资源目录

publicsphere-bg2.jpg
srcassetsimages...其余图片

three.js实现3d球体树状结构布局——添加入场、出场、点击放大等动画相关推荐

  1. JS 数组转树状结构

    需求: 将如下数组转成树状结构 // 需转化数组示例data = [{ id: '01', lable: '项目经理', pid: '' },{ id: '02', lable: '产品leader' ...

  2. 我的前端工具集(四)树状结构后篇

    我的前端工具集(四)树状结构后篇   liuyuhang原创,未经允许禁止转载 目录 我的前端工具集 上文连接 我的前端工具集(四)树状结构前偏 1.数据组织 在3.2.节有截图 2.树状结构代码 2 ...

  3. 使用jQuery Treeview插件实现树状结构效果

    首先到一个国外网站上下载Treeview插件: http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ 当然你也可以通过我博客的源文件 ...

  4. Jquery实现无限级树状结构并动态添加增删改等编辑功能

    点击打开链接 源:http://www.56gee.com/Detail/2012/04/11/8AEB74E423/ <!DOCTYPE html PUBLIC "-//W3C//D ...

  5. excel转json (树状结构)

    excel转json (树状结构) 一.python读取excel 转json 目的:在于将excel的数据转换成json格式. import xlrd, jsondef read_xlsx_file ...

  6. CoreAnimation图层的树状结构和寄宿图

    1.图层的树状结构 Core Animation的前身叫做Layer Kit,所以,你应该意识到Core Animation并不只是用来做动画的.做动画只是Core Animation特性的冰山一角. ...

  7. 用中值排序基数法实现树状结构 (转)

    在BBS的编写中,经常有人问怎样实现树状结构?一个比较不负责任的回答是:使用递归算法.当然,递归是一个可行的办法 (二叉树的历遍也好象只能使用递归算法),但对于BBS来说,这样做势必要进行大量的Sql ...

  8. 递归查询树状结构某个确定的节点

    递归 递归算法在日常工作中算是用的比较多的一种,比如DOM树的遍历,多层级树状结构的生成,遍历寻找某个树节点等 1 先来看下数据结构 var result = {id:0,name:"张飞& ...

  9. 系统管理模块_部门管理_设计(映射)本模块中的所有实体并总结设计实体的技巧_懒加载异常问题_树状结构...

    系统管理模块_部门管理_设计本模块中的所有实体并总结设计实体的技巧 设计实体流程 1,有几个实体? 一般是一组增删改查对应一个实体. 2,实体之间有什么关系? 一般是页面引用了其他的实体时,就表示与这 ...

最新文章

  1. Python---哈夫曼树---Huffman Tree
  2. 作业盒子完成1.5亿美元D轮融资,用AI普及教育资源
  3. python实现scp功能_转python实现ftp,scp的实现
  4. linux mount挂载设备(u盘,光盘,iso等 )使用说明
  5. python--Time(时间)模块
  6. 《Python Cookbook 3rd》笔记(4.4):实现迭代器协议
  7. Unity3d(UE4)动态加载osgb倾斜摄影数据
  8. Windows Vista和局域网聊天的计算机
  9. 07 熟练使用Console类实现从控制台输入输出数据 1214
  10. C++使用boost::bind 订阅消息中的返回函数传入多个参数
  11. 弯道超车:容器技术究竟为云计算带来了什么?
  12. 解决POI事件驱动模式读取不到Java代码创建的Excel表格数据问题
  13. ruoyi cloud配置启动
  14. Adb shell命令直接打开语言设置界面
  15. ios自动订阅服务器,iOS IAP - 自动续期订阅
  16. 基于闪电连接过程优化算法的函数寻优算法
  17. 中兴通讯2015笔试应用题
  18. 9017R单节锂电池线性充电管理 IC
  19. 会计科目主数据 由非成本要素变为成本要素
  20. Spring Boot-获取请求头中的参数

热门文章

  1. 如何才能成为一个合格的项目负责人?
  2. Linux命令 - whoami命令
  3. UTP和TCP他们的区别
  4. Linux :: 时间日历指令【1】:date 指令:格式化显示时间信息、Linux 下获取时间
  5. 云计算|OpenStack|社区版OpenStack安装部署文档(十三--- 自制镜像---Linux和Windows镜像)
  6. threejs 实现场景漫游效果(相机沿着自定义轨道移动)
  7. html,表格(table)属性中的 thead、tbody 以及 tfoot的关系
  8. initialize php,关于_initialize()的详细介绍
  9. 打篮球,听摇滚,敲键盘也能是人生赢家。程序员访谈(三)
  10. android linker入口