Given starting and ending points on a sphere and the center of the sphere, draw the great circle arc between the starting and ending points. If the starting and ending points have different radii, a segment of a logarithmic spiral will join them.

arc3d(from, to, center, radius, n, circle = 50, base = 0,
plot = TRUE, ...)

## Arguments

from

One or more points from which to start arcs.

to

One or more destination points.

center

One or more center points.

If not missing, a vector of length n giving the radii at each point between from and to. If missing, the starting and ending points will be joined by a logarithmic spiral.

n

If not missing, how many segments to use between the first and last point. If missing, a value will be calculated based on the angle between starting and ending points as seen from the center.

circle

How many segments would be used if the arc went completely around a circle.

base

See Details below.

plot

Should the arcs be plotted, or returned as a matrix?

...

Additional parameters to pass to points3d.

## Details

If any of from, to or center is an n by 3 matrix with n > 1, multiple arcs will be drawn by recycling each of these parameters to the number of rows of the longest one.

If the vector lengths of from - center and to - center differ, then instead of a spherical arc, the function will draw a segment of a logarithmic spiral joining the two points.

By default, the arc is drawn along the shortest great circle path from from to to, but the base parameter can be used to modify this. If base = 1 is used, the longer arc will be followed. Larger positive integer values will result in base - 1 loops in that direction completely around the sphere. Negative values will draw the curve in the same direction as the shortest arc, but with abs(base) full loops. It doesn't make much sense to ask for such loops unless the radii of from and to differ, because spherical arcs would overlap. Normally the base parameter is left at its default value of 0.

When base is non-zero, the curve will be constructed in multiple pieces, between from, to, -from and -to, for as many steps as necessary. If n is specified, it will apply to each of these pieces.

## Value

If plot = TRUE, called mainly for the side effect of drawing arcs. Invisibly returns the object ID of the collection of arcs. If plot = FALSE, returns a 3 column matrix containing the points that would be drawn as the arcs.

Duncan Murdoch

## Examples

normalize <- function(v) v/sqrt(sum(v^2))

# These vectors all have the same length

from <- t(apply(matrix(rnorm(9), ncol = 3), 1, normalize))
to <- normalize(rnorm(3))
center <- c(0, 0, 0)

open3d()
spheres3d(center, radius = 1, col = "white", alpha = 0.2)

arc3d(from, to, center, col = "red")
arc3d(from, 2*to, center, col = "blue")

text3d(rbind(from, to, center, 2*to),
text = c(paste0("from", 1:3), "to", "center", "2*to"),
depth_mask = FALSE, depth_test = "always")

{"x":{"material":{"color":"#000000","alpha":1,"lit":true,"ambient":"#000000","specular":"#FFFFFF","emission":"#000000","shininess":50,"smooth":true,"front":"filled","back":"filled","size":3,"lwd":1,"fog":true,"point_antialias":false,"line_antialias":false,"texture":null,"textype":"rgb","texmipmap":false,"texminfilter":"linear","texmagfilter":"linear","texenvmap":false,"depth_mask":true,"depth_test":"less","isTransparent":false,"polygon_offset":[0,0],"margin":"","floating":false,"tag":""},"rootSubscene":51,"objects":{"57":{"id":57,"type":"spheres","material":{"alpha":0.200000002980232,"isTransparent":true},"vertices":"0","colors":"1","radii":[[1]],"centers":"2","ignoreExtent":false,"fastTransparency":true,"flags":32811},"58":{"id":58,"type":"linestrip","material":{"lit":false},"vertices":"3","colors":"4","centers":"5","ignoreExtent":false,"flags":32832},"59":{"id":59,"type":"linestrip","material":{"lit":false},"vertices":"6","colors":"7","centers":"8","ignoreExtent":false,"flags":32832},"60":{"id":60,"type":"text","material":{"lit":false,"depth_mask":false,"depth_test":"always"},"vertices":"9","colors":"10","texts":[["from1"],["from2"],["from3"],["to"],["center"],["2*to"]],"cex":[[1]],"adj":[[0.5,0.5,0.5]],"centers":"11","family":[["sans"]],"font":[[1]],"ignoreExtent":false,"flags":33808},"55":{"id":55,"type":"light","vertices":[[0,0,1]],"colors":[[1,1,1,1],[1,1,1,1],[1,1,1,1]],"viewpoint":true,"finite":false},"54":{"id":54,"type":"background","material":{},"colors":"12","centers":"13","sphere":false,"fogtype":"none","fogscale":1,"flags":32768},"56":{"id":56,"type":"background","material":{"lit":false,"back":"lines"},"colors":"14","centers":"15","sphere":false,"fogtype":"none","fogscale":1,"flags":32768},"51":{"id":51,"type":"subscene","par3d":{"antialias":8,"FOV":30,"ignoreExtent":false,"listeners":51,"mouseMode":{"none":"none","left":"trackball","right":"zoom","middle":"fov","wheel":"pull"},"observer":[0,0,7.8545036315918],"modelMatrix":[[1,0,0,0],[0,0.342020153999329,0.939692616462708,-0.162018805742264],[0,-0.939692616462708,0.342020153999329,-8.17139434814453],[0,0,0,1]],"projMatrix":[[3.73205089569092,0,0,0],[0,3.73205089569092,0,0],[0,0,-3.86370348930359,-28.3145771026611],[0,0,-1,0]],"skipRedraw":false,"userMatrix":[[1,0,0,0],[0,0.342020143325668,0.939692620785909,0],[0,-0.939692620785909,0.342020143325668,0],[0,0,0,1]],"userProjection":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],"scale":[1,1,1],"viewport":{"x":0,"y":0,"width":1,"height":1},"zoom":1,"bbox":[-1,1,-1.48473143577576,1,-1,1.52126145362854],"windowRect":[40,85,296,341],"family":"sans","font":1,"cex":1,"useFreeType":true,"fontname":"/System/Library/Fonts/Supplemental/Arial.ttf","maxClipPlanes":6,"glVersion":2.1,"activeSubscene":0},"embeddings":{"viewport":"replace","projection":"replace","model":"replace","mouse":"replace"},"objects":[56,57,58,59,60,55],"subscenes":[],"flags":34171}},"crosstalk":{"key":[],"group":[],"id":[],"options":[]},"width":480,"height":480,"buffer":{"accessors":[{"bufferView":0,"componentType":5121,"count":1,"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":1,"type":"VEC4"},{"bufferView":2,"componentType":5121,"count":1,"type":"VEC3"},{"bufferView":3,"componentType":5126,"count":40,"type":"VEC3"},{"bufferView":4,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":5,"componentType":5126,"count":40,"type":"VEC3"},{"bufferView":6,"componentType":5126,"count":40,"type":"VEC3"},{"bufferView":7,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":8,"componentType":5126,"count":40,"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":6,"type":"VEC3"},{"bufferView":10,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":11,"componentType":5126,"count":6,"type":"VEC3"},{"bufferView":12,"componentType":5126,"count":1,"type":"VEC4"},{"bufferView":13,"componentType":5121,"count":1,"type":"VEC3"},{"bufferView":14,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":15,"componentType":5121,"count":1,"type":"VEC3"}],"bufferViews":[{"buffer":0,"byteLength":3,"byteOffset":0},{"buffer":0,"byteLength":16,"byteOffset":4},{"buffer":0,"byteLength":3,"byteOffset":20},{"buffer":0,"byteLength":480,"byteOffset":24},{"buffer":0,"byteLength":4,"byteOffset":504},{"buffer":0,"byteLength":480,"byteOffset":508},{"buffer":0,"byteLength":480,"byteOffset":988},{"buffer":0,"byteLength":4,"byteOffset":1468},{"buffer":0,"byteLength":480,"byteOffset":1472},{"buffer":0,"byteLength":72,"byteOffset":1952},{"buffer":0,"byteLength":4,"byteOffset":2024},{"buffer":0,"byteLength":72,"byteOffset":2028},{"buffer":0,"byteLength":16,"byteOffset":2100},{"buffer":0,"byteLength":3,"byteOffset":2116},{"buffer":0,"byteLength":4,"byteOffset":2119},{"buffer":0,"byteLength":3,"byteOffset":2123}],"buffers":[{"byteLength":2126,"bytes":"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\nmD6ZmJg+AACAPwAAAAEBAQEAAAA="}]},"context":{"shiny":false,"rmarkdown":null},"vertexShader":"#line 2 1\n// File 1 is the vertex shader\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\n\nattribute vec3 aPos;\nattribute vec4 aCol;\nuniform mat4 mvMatrix;\nuniform mat4 prMatrix;\nvarying vec4 vCol;\nvarying vec4 vPosition;\n\n#ifdef NEEDS_VNORMAL\nattribute vec3 aNorm;\nuniform mat4 normMatrix;\nvarying vec4 vNormal;\n#endif\n\n#if defined(HAS_TEXTURE) || defined (IS_TEXT)\nattribute vec2 aTexcoord;\nvarying vec2 vTexcoord;\n#endif\n\n#ifdef FIXED_SIZE\nuniform vec3 textScale;\n#endif\n\n#ifdef FIXED_QUADS\nattribute vec3 aOfs;\n#endif\n\n#ifdef IS_TWOSIDED\n#ifdef HAS_NORMALS\nvarying float normz;\nuniform mat4 invPrMatrix;\n#else\nattribute vec3 aPos1;\nattribute vec3 aPos2;\nvarying float normz;\n#endif\n#endif // IS_TWOSIDED\n\n#ifdef FAT_LINES\nattribute vec3 aNext;\nattribute vec2 aPoint;\nvarying vec2 vPoint;\nvarying float vLength;\nuniform float uAspect;\nuniform float uLwd;\n#endif\n\n\nvoid main(void) {\n  \n#ifndef IS_BRUSH\n#if defined(NCLIPPLANES) || !defined(FIXED_QUADS) || defined(HAS_FOG)\n  vPosition = mvMatrix * vec4(aPos, 1.);\n#endif\n  \n#ifndef FIXED_QUADS\n  gl_Position = prMatrix * vPosition;\n#endif\n#endif // !IS_BRUSH\n  \n#ifdef IS_POINTS\n  gl_PointSize = POINTSIZE;\n#endif\n  \n  vCol = aCol;\n  \n#ifdef NEEDS_VNORMAL\n  vNormal = normMatrix * vec4(-aNorm, dot(aNorm, aPos));\n#endif\n  \n#ifdef IS_TWOSIDED\n#ifdef HAS_NORMALS\n  /* normz should be calculated *after* projection */\n  normz = (invPrMatrix*vNormal).z;\n#else\n  vec4 pos1 = prMatrix*(mvMatrix*vec4(aPos1, 1.));\n  pos1 = pos1/pos1.w - gl_Position/gl_Position.w;\n  vec4 pos2 = prMatrix*(mvMatrix*vec4(aPos2, 1.));\n  pos2 = pos2/pos2.w - gl_Position/gl_Position.w;\n  normz = pos1.x*pos2.y - pos1.y*pos2.x;\n#endif\n#endif // IS_TWOSIDED\n  \n#ifdef NEEDS_VNORMAL\n  vNormal = vec4(normalize(vNormal.xyz/vNormal.w), 1);\n#endif\n  \n#if defined(HAS_TEXTURE) || defined(IS_TEXT)\n  vTexcoord = aTexcoord;\n#endif\n  \n#if defined(FIXED_SIZE) && !defined(ROTATING)\n  vec4 pos = prMatrix * mvMatrix * vec4(aPos, 1.);\n  pos = pos/pos.w;\n  gl_Position = pos + vec4(aOfs*textScale, 0.);\n#endif\n  \n#if defined(IS_SPRITES) && !defined(FIXED_SIZE)\n  vec4 pos = mvMatrix * vec4(aPos, 1.);\n  pos = pos/pos.w + vec4(aOfs,  0.);\n  gl_Position = prMatrix*pos;\n#endif\n  \n#ifdef FAT_LINES\n  /* This code was inspired by Matt Deslauriers' code in \n   https://mattdesl.svbtle.com/drawing-lines-is-hard */\n  vec2 aspectVec = vec2(uAspect, 1.0);\n  mat4 projViewModel = prMatrix * mvMatrix;\n  vec4 currentProjected = projViewModel * vec4(aPos, 1.0);\n  currentProjected = currentProjected/currentProjected.w;\n  vec4 nextProjected = projViewModel * vec4(aNext, 1.0);\n  vec2 currentScreen = currentProjected.xy * aspectVec;\n  vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n  float len = uLwd;\n  vec2 dir = vec2(1.0, 0.0);\n  vPoint = aPoint;\n  vLength = length(nextScreen - currentScreen)/2.0;\n  vLength = vLength/(vLength + len);\n  if (vLength > 0.0) {\n    dir = normalize(nextScreen - currentScreen);\n  }\n  vec2 normal = vec2(-dir.y, dir.x);\n  dir.x /= uAspect;\n  normal.x /= uAspect;\n  vec4 offset = vec4(len*(normal*aPoint.x*aPoint.y - dir), 0.0, 0.0);\n  gl_Position = currentProjected + offset;\n#endif\n  \n#ifdef IS_BRUSH\n  gl_Position = vec4(aPos, 1.);\n#endif\n}","fragmentShader":"#line 2 2\n// File 2 is the fragment shader\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\nvarying vec4 vCol; // carries alpha\nvarying vec4 vPosition;\n#if defined(HAS_TEXTURE) || defined (IS_TEXT)\nvarying vec2 vTexcoord;\nuniform sampler2D uSampler;\n#endif\n\n#ifdef HAS_FOG\nuniform int uFogMode;\nuniform vec3 uFogColor;\nuniform vec4 uFogParms;\n#endif\n\n#if defined(IS_LIT) && !defined(FIXED_QUADS)\nvarying vec4 vNormal;\n#endif\n\n#if NCLIPPLANES > 0\nuniform vec4 vClipplane[NCLIPPLANES];\n#endif\n\n#if NLIGHTS > 0\nuniform mat4 mvMatrix;\n#endif\n\n#ifdef IS_LIT\nuniform vec3 emission;\nuniform float shininess;\n#if NLIGHTS > 0\nuniform vec3 ambient[NLIGHTS];\nuniform vec3 specular[NLIGHTS]; // light*material\nuniform vec3 diffuse[NLIGHTS];\nuniform vec3 lightDir[NLIGHTS];\nuniform bool viewpoint[NLIGHTS];\nuniform bool finite[NLIGHTS];\n#endif\n#endif // IS_LIT\n\n#ifdef IS_TWOSIDED\nuniform bool front;\nvarying float normz;\n#endif\n\n#ifdef FAT_LINES\nvarying vec2 vPoint;\nvarying float vLength;\n#endif\n\nvoid main(void) {\n  vec4 fragColor;\n#ifdef FAT_LINES\n  vec2 point = vPoint;\n  bool neg = point.y < 0.0;\n  point.y = neg ? (point.y + vLength)/(1.0 - vLength) :\n                 -(point.y - vLength)/(1.0 - vLength);\n#if defined(IS_TRANSPARENT) && defined(IS_LINESTRIP)\n  if (neg && length(point) <= 1.0) discard;\n#endif\n  point.y = min(point.y, 0.0);\n  if (length(point) > 1.0) discard;\n#endif // FAT_LINES\n  \n#ifdef ROUND_POINTS\n  vec2 coord = gl_PointCoord - vec2(0.5);\n  if (length(coord) > 0.5) discard;\n#endif\n  \n#if NCLIPPLANES > 0\n  for (int i = 0; i < NCLIPPLANES; i++)\n    if (dot(vPosition, vClipplane[i]) < 0.0) discard;\n#endif\n    \n#ifdef FIXED_QUADS\n    vec3 n = vec3(0., 0., 1.);\n#elif defined(IS_LIT)\n    vec3 n = normalize(vNormal.xyz);\n#endif\n    \n#ifdef IS_TWOSIDED\n    if ((normz <= 0.) != front) discard;\n#endif\n    \n#ifdef IS_LIT\n    vec3 eye = normalize(-vPosition.xyz/vPosition.w);\n    vec3 lightdir;\n    vec4 colDiff;\n    vec3 halfVec;\n    vec4 lighteffect = vec4(emission, 0.);\n    vec3 col;\n    float nDotL;\n#ifdef FIXED_QUADS\n    n = -faceforward(n, n, eye);\n#endif\n    \n#if NLIGHTS > 0\n    for (int i=0;i<NLIGHTS;i++) {\n      colDiff = vec4(vCol.rgb * diffuse[i], vCol.a);\n      lightdir = lightDir[i];\n      if (!viewpoint[i])\n        lightdir = (mvMatrix * vec4(lightdir, 1.)).xyz;\n      if (!finite[i]) {\n        halfVec = normalize(lightdir + eye);\n      } else {\n        lightdir = normalize(lightdir - vPosition.xyz/vPosition.w);\n        halfVec = normalize(lightdir + eye);\n      }\n      col = ambient[i];\n      nDotL = dot(n, lightdir);\n      col = col + max(nDotL, 0.) * colDiff.rgb;\n      col = col + pow(max(dot(halfVec, n), 0.), shininess) * specular[i];\n      lighteffect = lighteffect + vec4(col, colDiff.a);\n    }\n#endif\n    \n#else // not IS_LIT\n    vec4 colDiff = vCol;\n    vec4 lighteffect = colDiff;\n#endif\n    \n#ifdef IS_TEXT\n    vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n#endif\n    \n#ifdef HAS_TEXTURE\n#ifdef TEXTURE_rgb\n    vec4 textureColor = lighteffect*vec4(texture2D(uSampler, vTexcoord).rgb, 1.);\n#endif\n    \n#ifdef TEXTURE_rgba\n    vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n#endif\n    \n#ifdef TEXTURE_alpha\n    vec4 textureColor = texture2D(uSampler, vTexcoord);\n    float luminance = dot(vec3(1.,1.,1.), textureColor.rgb)/3.;\n    textureColor =  vec4(lighteffect.rgb, lighteffect.a*luminance);\n#endif\n    \n#ifdef TEXTURE_luminance\n    vec4 textureColor = vec4(lighteffect.rgb*dot(texture2D(uSampler, vTexcoord).rgb, vec3(1.,1.,1.))/3., lighteffect.a);\n#endif\n    \n#ifdef TEXTURE_luminance_alpha\n    vec4 textureColor = texture2D(uSampler, vTexcoord);\n    float luminance = dot(vec3(1.,1.,1.),textureColor.rgb)/3.;\n    textureColor = vec4(lighteffect.rgb*luminance, lighteffect.a*textureColor.a);\n#endif\n    \n    fragColor = textureColor;\n\n#elif defined(IS_TEXT)\n    if (textureColor.a < 0.1)\n      discard;\n    else\n      fragColor = textureColor;\n#else\n    fragColor = lighteffect;\n#endif // HAS_TEXTURE\n    \n#ifdef HAS_FOG\n    // uFogParms elements: x = near, y = far, z = fogscale, w = (1-sin(FOV/2))/(1+sin(FOV/2))\n    // In Exp and Exp2: use density = density/far\n    // fogF will be the proportion of fog\n    // Initialize it to the linear value\n    float fogF;\n    if (uFogMode > 0) {\n      fogF = (uFogParms.y - vPosition.z/vPosition.w)/(uFogParms.y - uFogParms.x);\n      if (uFogMode > 1)\n        fogF = mix(uFogParms.w, 1.0, fogF);\n      fogF = fogF*uFogParms.z;\n      if (uFogMode == 2)\n        fogF = 1.0 - exp(-fogF);\n      // Docs are wrong: use (density*c)^2, not density*c^2\n      // https://gitlab.freedesktop.org/mesa/mesa/-/blob/master/src/mesa/swrast/s_fog.c#L58\n      else if (uFogMode == 3)\n        fogF = 1.0 - exp(-fogF*fogF);\n      fogF = clamp(fogF, 0.0, 1.0);\n      gl_FragColor = vec4(mix(fragColor.rgb, uFogColor, fogF), fragColor.a);\n    } else gl_FragColor = fragColor;\n#else\n    gl_FragColor = fragColor;\n#endif // HAS_FOG\n    \n}","players":[],"webGLoptions":{"preserveDrawingBuffer":true}},"evals":[],"jsHooks":[]}