Geometry in affiner

Angle objects

In {affiner} angles are represented by the angle() class:

  • Supports the following angular units (note we ignore any punctuation and space characters as well as any trailing s’s e.g. “half turns” will be treated as equivalent to “halfturn”):

    • “deg” or “degree”
    • “half-revolution”, “half-turn”, or “pi-radian”
    • “gon”, “grad”, “grade”, or “gradian”
    • “rad” or “radian”
    • “rev”, “revolution”, “tr”, or “turn”
  • degrees(), gradians(), pi_radians(), radians(), turns() are convenience wrappers around as_angle.() that specifies the angular unit.

  • One can use the affiner_angular_unit global option to set the default angular unit used by this package from “degrees” to “gradians”, (multiples of) “pi-radians”, “radians”, or “turns”.

  • Use is_congruent() to check if two angles are congruent modulo full turns.

library("affiner")
as_angle(90, "degrees") + turns(1)
## <angle<degrees>[1]>
## [1] 450°
is_congruent(degrees(180), radians(pi))
## [1] TRUE
as.numeric(turns(1/3), "radians")
## [1] 2.094395

Trigonometry

{affiner} provides several angle() class aware trigonometric functions:

  • sine(), cosine(), tangent(), secant(), cosecant(), cotangent(),
  • arcsine(), arccosine(), arctangent(), arcsecant(), arccosecant(), and arccotangent(). arcsine() and arccosine() also feature a tolerance value so that values that exceed the 1 / -1 cutoffs by a small tolerance are rounded to those values.
  • One can also use the base S3 methods sin(), cos(), and tan() on angle() objects.
library("affiner")
sin(2 * pi)
## [1] -2.449294e-16
sine(degrees(360))
## [1] 0
arctangent(x = 0, y = 1)
## <angle<degrees>[1]>
## [1] 90°

2D Coordinates

In {affiner} 2D Coordinates are represented by a Coord2D R6 class:

  • Create Coord2D objects with as_coord2d()

  • Coord2D R6 objects supports several affine transformation methods that can be chained:

    • permute()
    • project()
    • reflect()
    • rotate()
    • scale()
    • shear()
    • translate()
    • transform()
    • R6 method chained affine transformation matrices are auto-multiplied so you don’t need to manually multiply them for efficiency reasons.
    • {affiner} affine transformations are post-multiplied so affine transformations can be applied in an intuitive order.
    • abs() computes Euclidean norm and distance2d() computes Euclidean distances
    • convex_hull2d() computes the convex hull.
    • range() computes the axis-aligned bounding box ranges.
# Cartesian coordinates
library("affiner")
p <- as_coord2d(x = 1:10, y = 1:10)
print(p)
## <Coord2D[10]>
##        x  y w
##  [1,]  1  1 1
##  [2,]  2  2 1
##  [3,]  3  3 1
##  [4,]  4  4 1
##  [5,]  5  5 1
##  [6,]  6  6 1
##  [7,]  7  7 1
##  [8,]  8  8 1
##  [9,]  9  9 1
## [10,] 10 10 1
p2 <- p$
    clone()$
    scale(x = 0.5)$
    rotate(degrees(90))$
    reflect(as_line2d("y-axis"))$
    translate(as_coord2d(x = 0.5, y = 0.5))$
    print()
## <Coord2D[10]>
##         x   y w
##  [1,] 1.0 1.0 1
##  [2,] 1.5 1.5 1
##  [3,] 2.0 2.0 1
##  [4,] 2.5 2.5 1
##  [5,] 3.0 3.0 1
##  [6,] 3.5 3.5 1
##  [7,] 4.0 4.0 1
##  [8,] 4.5 4.5 1
##  [9,] 5.0 5.0 1
## [10,] 5.5 5.5 1
# Polar coordinates
theta <- degrees(seq(0, 300, by = 60))
radius <- 1
p <- as_coord2d(theta, radius = radius)
is_congruent(as_angle(p), theta) |> all()
## [1] TRUE
is_congruent(abs(p), radius) |> all()
## [1] TRUE

3D Coordinates

In {affiner} 3D Coordinates are represented by a Coord3D R6 class:

  • Create Coord3D objects with as_coord3d()

  • Coord3D R6 objects supports several affine transformation methods that can be chained:

    • permute()
    • project()
    • reflect()
    • rotate()
    • scale()
    • shear()
    • translate()
    • transform()
    • R6 method chained affine transformation matrices are auto-multiplied so you don’t need to manually pre-multiply them for efficiency reasons.
    • {affiner} affine transformations are post-multiplied so affine transformations can be applied in an intuitive order.
    • abs() computes Euclidean norm and distance3d() computes Euclidean distances
    • range() computes the axis-aligned bounding box ranges.
    • cross_product3d() computes cross products (* computes inner products).
# Cartesian coordinates
library("affiner")
p <- as_coord3d(x = 1:10, y = 1:10, z = 1:10)
print(p)
## <Coord3D[10]>
##        x  y  z w
##  [1,]  1  1  1 1
##  [2,]  2  2  2 1
##  [3,]  3  3  3 1
##  [4,]  4  4  4 1
##  [5,]  5  5  5 1
##  [6,]  6  6  6 1
##  [7,]  7  7  7 1
##  [8,]  8  8  8 1
##  [9,]  9  9  9 1
## [10,] 10 10 10 1
p2 <- p$
    clone()$
    scale(z = 0.5)$
    rotate(axis = as_coord3d("z-axis"), theta = degrees(90))$
    reflect(as_plane3d("yz-plane"))$
    shear(xy_shear = 0.5)$
    translate(as_coord3d(x = 0.5, y = 0.5, z = 0.5))$
    print()
## <Coord3D[10]>
##          x    y   z w
##  [1,]  2.0  1.5 1.0 1
##  [2,]  3.5  2.5 1.5 1
##  [3,]  5.0  3.5 2.0 1
##  [4,]  6.5  4.5 2.5 1
##  [5,]  8.0  5.5 3.0 1
##  [6,]  9.5  6.5 3.5 1
##  [7,] 11.0  7.5 4.0 1
##  [8,] 12.5  8.5 4.5 1
##  [9,] 14.0  9.5 5.0 1
## [10,] 15.5 10.5 5.5 1
# Spherical coordinates
inclination <- as_angle(p, type = "inclination")
azimuth <- as_angle(p, type = "azimuth")
radius <- abs(p)
ps <- as_coord3d(azimuth, radius = radius, inclination = inclination)
all.equal(p, ps)
## [1] TRUE
# Cylindrical coordinates
radius <- as_coord2d(p, plane = "xy-plane") |> abs()
pc <- as_coord3d(azimuth, radius = radius, z = p$z)
all.equal(p, pc)
## [1] TRUE

Orthographic/Axonometric and Oblique Projections

{affiner} can project Coord3D objects to Coord2D objects using orthographic/axonometric and oblique projections:

  • For a multiview/primary orthographic projection onto the xy-plane use as_coord2d(x)

  • For a multiview/primary orthographic projection onto the xz-plane use as_coord2d(x, permutation = "xzy")

  • For a “cabinet” oblique projection onto the xy-plane use as_coord2d(x, scale = 0.5)

  • For a “cabinet” oblique projection onto the xz-plane use as_coord2d(x, permutation = "xzy", scale = 0.5)

  • For other oblique projections manipulate the scale parameter (usually from 0.5 to 1.0) and the alpha angle parameter (usually from 30° to 45°).

  • For one “isometric” axonometric projection one can use

    x$
      clone()$
      translate(-mean(x)$
      rotate("z-axis", degrees(45))$
      rotate("x-axis", degrees(-90 + 35.264)) |>
      as_coord2d()
  • Other axonometric projections can be achieved with the right 3D rotations

  • See vignette("affiner", package = "affiner") for some visual examples

  • Recall one can use “scale” affine transformation to flip signs of x/y/z axes and “permute” affine transformation to switch order of x/y/z coordinates