@Alasdair-McAndrew I tried to find code that does this exactly, but failed. So I took some time try different combinations of atan acos asin until I got somewhat working solution. Then I added fixes for special cases that break the inital code. based on your suggestion I tried some values (dx=0, dy=0, dz=0) and found a case that did not work: (dx==0 && dy == 0). I have added an exception that now fixes taht edge case. (I am proud I made this work somehow, so if you find another edge case that does not work, let me know ) link1: gist: https://gist.github.com/hrgdavor/c1bc1b4f3e3f92161eb4dd9074363793 link2: jscad.xyz with that gist: https://jscad.xyz/#https://gist.githubusercontent.com/hrgdavor/c1bc1b4f3e3f92161eb4dd9074363793/raw/1c41bbf3dc5d5af33614205ce8bd17e1e93b66f1/testCylinderFromTo.js new version with fix: function cylinderFromTo(p1,p2, radius){ const sqr = x=>x*x let dx = p2[0] - p1[0] let dy = p2[1] - p1[1] let dz = p2[2] - p1[2] let height = Math.sqrt( sqr(dx) + sqr(dy) + sqr(dz)) let obj = cylinder({radius, height}) if(dx || dy){ let dxy = Math.sqrt( sqr(dx) + sqr(dy)) let ay = Math.atan(dxy/dz) *(dx < 0 ? -1:1) let az = Math.atan(dy/dx) let ax = dz < 0 ? -Math.PI:0 obj = transforms.rotate([ax,ay,az], obj) } let mid = [p1[0]+dx/2,p1[1]+dy/2,p1[2]+dz/2] return translate(mid, obj) } the code now works for all of these: testCylinderFromTo([-20,30,50],[-40,-10,70]); testCylinderFromTo([0,0,0],[10,10,10]) testCylinderFromTo([0,0,0],[20,-20,20]) testCylinderFromTo([0,0,0],[-30,30,30]) testCylinderFromTo([0,0,0],[-40,-40,40]) testCylinderFromTo([0,0,0],[15,15,-15]) testCylinderFromTo([0,0,0],[25,-25,-25]) testCylinderFromTo([0,0,0],[-35,35,-35]) testCylinderFromTo([100,0,0],[100,0,30]) testCylinderFromTo([100,0,0],[100,0,-30]) testCylinderFromTo([100,0,0],[100,30,0]) testCylinderFromTo([100,0,0],[100,-30,0]) testCylinderFromTo([100,0,0],[130,0,0]) testCylinderFromTo([100,0,0],[70,0,0]) screen: