需求来源

最近的一个项目,需要将公司的各项技术和应用的关系,以3D的形式呈现出来。如果直接用直线连接,会密密麻麻很难看,分不清具体的关系。因此我们考虑通过边绑定的方式,对同一个源头和目标的连线,做一些聚合。我写了个Demo,效果如上图所示。
思路
大致分为如下几个步骤:
关键代码
计算N个点的中心点
这个如果要严格计算,会比较麻烦,得构建三角形,求每个三角形重心,然后根据重心算中心点。
我为了简单,直接用这N个点的x、y、z求平均值,作为中心点,效果看起来还可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
getCenterOfTechModules(edges: Array<Edge>): [number, number, number] { let totalX = 0; let totalY = 0; let totalZ = 0; edges.forEach(edge => { const techModuleNode = this.getNodeById(edge.from) as Node; const [x, y, z] = techModuleNode.position as [number, number, number]; totalX += x; totalY += y; totalZ += z; });
return [totalX / edges.length, totalY / edges.length, totalZ / edges.length]; }
|
计算3D空间中,直线上的点
参考这个文章:
https://blog.csdn.net/weixin_42795611/article/details/120796681
我是按百分比取点的,因此参数中有个percent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
getPointByPercent(vector1: Vector3, vector2: Vector3, percent: number) { function getLength(vector: Vector3) { const x = vector.x ** 2; const y = vector.y ** 2; const z = vector.z ** 2; return Math.sqrt(x + y + z); }
function getEquationParameters(v1: Vector3, v2: Vector3) { const direction: Vector3 = v2.sub(v1); const lengthOfVector = getLength(direction);
const normalizedVector = new Vector3(); normalizedVector.copy(direction); normalizedVector.normalize(); const lengthOfNormalizedVector = getLength(normalizedVector);
return { dx: normalizedVector.x, dy: normalizedVector.y, dz: normalizedVector.z, t: lengthOfVector / lengthOfNormalizedVector }; }
const { dx, dy, dz, t } = getEquationParameters(vector1, vector2); return new Vector3( vector1.x + dx * t * percent, vector1.y + dy * t * percent, vector1.z + dz * t * percent ); }
|
绘制曲线
算出绘制曲线所需的点后,直接用THREE.CatmullRomCurve3就可以画出来了。
为什么没有用贝塞尔曲线(THREE.CubicBezierCurve3):因为贝塞尔中间那段无法收拢。
贝塞尔曲线与 CatmullRom 曲线的区别在于,CatmullRom 曲线可以平滑的通过所有点,一般用于绘制轨迹,而贝塞尔曲线通过中间点来构造切线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
export function drawCurveLine( vectors: Array<Vector3>, materialParameters: LineBasicMaterialParameters = { color: 0xcccccc }, pointCount = 50 ) { const curve2 = new CatmullRomCurve3(vectors);
const points2 = curve2.getPoints(pointCount); const geometry2 = new BufferGeometry().setFromPoints(points2);
const material2 = new LineBasicMaterial(materialParameters);
return new Line(geometry2, material2); }
drawCurveEdges(edges: Array<Edge>, vectors: Array<Vector3>) { const verticalLineLengthOfTechModule = 0.5; const verticalLineLengthOfApp = 0.2; edges.forEach(techModuleEdge => { const techModuleNode = this.getNodeById(techModuleEdge.from); const appNode = this.getNodeById(techModuleEdge.to);
const [x, y, z] = techModuleNode.position as [number, number, number]; const points = [ new Vector3(...(appNode.position as [number, number, number])), new Vector3( appNode.position[0], appNode.position[1] + verticalLineLengthOfApp, appNode.position[2] ), ...vectors, new Vector3(x, y - verticalLineLengthOfTechModule, z), new Vector3(x, y, z) ];
const line = drawCurveLine(points);
this._scene.add(line); }); }
|
资料
(TODO)山东大学可视化实验室的边绑定算法:
https://zhuanlan.zhihu.com/p/149112508