# Trigonometry


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

<div>

> **Coding style notes**
>
> Throughout, we provide type signatures for all functions using
> [jaxtyping](https://docs.kidger.site/jaxtyping). We use `jnp` (=
> `jax.numpy`) instead of `numpy`, and follow JAX’s functional
> programming paradigm (see [JAX — the sharp
> bits](https://docs.jax.dev/en/latest/notebooks/Common_Gotchas_in_JAX.html)).
>
> **Safe division.** To avoid division by zero in degenerate
> configurations (e.g. zero-area triangles), all divisors are clamped
> via `jnp.clip(x, 1e-12)`.

</div>

### Triangle areas and circumcenters

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L77"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_polygon_area

``` python

def get_polygon_area(
    pts:Float[Array, 'n_vertices 2'], # Ordered polygon vertices.
)->Float[Array, '']: # Signed area.

```

*Signed area of a 2D simple polygon (shoelace formula).*

Positive for counter-clockwise vertex ordering.

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L59"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_triangle_area

``` python

def get_triangle_area(
    a:Float[Array, 'dim'], b:Float[Array, 'dim'], c:Float[Array, 'dim']
)->Float[Array, '']: # Triangle area.

```

*Unsigned area of triangle with vertices a, b, c.*

------------------------------------------------------------------------

### get_triangle_area_from_sides

``` python

def get_triangle_area_from_sides(
    u:Float[Array, 'dim'], v:Float[Array, 'dim']
)->Float[Array, '']: # Triangle area.

```

*Unsigned area of triangle edge vectors u, v*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L41"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_oriented_triangle_area

``` python

def get_oriented_triangle_area(
    a:Float[Array, 'dim'], b:Float[Array, 'dim'], c:Float[Array, 'dim']
)->Float[Array, '*']: # Scalar (dim=2) or area-weighted normal vector (dim=3).

```

*Signed area of triangle with vertices a, b, c.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L19"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_circumcenter

``` python

def get_circumcenter(
    a:Float[Array, 'dim'], b:Float[Array, 'dim'], c:Float[Array, 'dim']
)->Float[Array, 'dim']: # Circumcenter coordinates.

```

*Circumcenter of triangle with vertices a, b, c via barycentric
coordinates.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L96"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_voronoi_corner_area

``` python

def get_voronoi_corner_area(
    a:Float[Array, 'dim'], b:Float[Array, 'dim'], c:Float[Array, 'dim'], zero_clip:float=1e-10
)->Float[Array, '*']:

```

*Compute Voronoi area at corner a of triangle abc.* Returns zero for a
degenerate triangle.

### Vector operations

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L199"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_cot_between_vectors

``` python

def get_cot_between_vectors(
    a:Float[Array, 'dim'], b:Float[Array, 'dim']
)->Float[Array, '']: # Cotangent value.

```

*Cotangent of the angle between vectors a and b.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L182"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_angle_between_vectors

``` python

def get_angle_between_vectors(
    a:Float[Array, 'dim'], b:Float[Array, 'dim']
)->Float[Array, '']: # Angle in radians, in $[0, \pi]$.

```

*Unsigned angle between vectors a and b.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L165"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_signed_angle_between_vectors

``` python

def get_signed_angle_between_vectors(
    a:Float[Array, '2'], b:Float[Array, '2']
)->Float[Array, '']: # Signed angle in radians, in $(-\pi, \pi]$.

```

*Signed angle from 2D vector a to b (CCW positive).*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L148"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_projector

``` python

def get_projector(
    normal:Float[Array, 'dim'], # Normal vector (need not be unit length).
)->Float[Array, 'dim dim']: # Projection matrix onto the plane orthogonal to normal.

```

*Tangent-plane projector *P* = *I* − *n̂* ⊗ *n̂*.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L129"
target="_blank" style="float:right; font-size:smaller">source</a>

### project_out_vector

``` python

def project_out_vector(
    a:Float[Array, 'dim'], # Input vector.
    b:Float[Array, 'dim'], # Normal direction to project out.
)->Float[Array, 'dim']: # Component of a orthogonal to b.

```

*Project vector a onto the plane orthogonal to b.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L110"
target="_blank" style="float:right; font-size:smaller">source</a>

### project_on_vector

``` python

def project_on_vector(
    a:Float[Array, 'dim'], # Vector to project.
    b:Float[Array, 'dim'], # Target direction.
)->Float[Array, 'dim']: # Projection of a onto b.

```

*Project vector a onto vector b.*

``` python
key1 = jax.random.key(0)
key2 = jax.random.key(1)

a, b = jax.random.normal(key1, shape=2), jax.random.normal(key2, shape=2)
assert get_triangle_area_from_sides(a, b) == jnp.linalg.norm(jnp.cross(a, b))/2

a, b = jax.random.normal(key1, shape=3), jax.random.normal(key2, shape=3)
assert get_triangle_area_from_sides(a, b) == jnp.linalg.norm(jnp.cross(a, b))/2
```

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L216"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_tetrahedron_volume

``` python

def get_tetrahedron_volume(
    a:Float[Array, '3'], b:Float[Array, '3'], c:Float[Array, '3']
)->Float[Array, '']: # Signed volume (positive if a, b, c form a right-handed frame).

```

*Signed volume of tetrahedron with edge vectors a, b, c from a common
vertex.*

### Intrinsic geometry

Many mesh quantities (angles, triangle areas, …) can be computed purely
intrinsically from edge lengths ℓ<sub>*i**j*</sub> without explicit
reference to the mesh vertex coordinates in 3d.

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L309"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_circumcenter_from_lengths

``` python

def get_circumcenter_from_lengths(
    la:Float[Array, ''], # Length of side opposite vertex a (i.e. edge bc).
    lb:Float[Array, ''], # Length of side opposite vertex b (i.e. edge ca).
    lc:Float[Array, ''], # Length of side opposite vertex c (i.e. edge ab).
)->Float[Array, '3']: # Normalized barycentric coordinates [lambda_a, lambda_b, lambda_c].

```

*Circumcenter in barycentric coordinates from edge lengths.*

To recover Cartesian coordinates:
*u* = *λ*<sub>*a*</sub> *a* + *λ*<sub>*b*</sub> *b* + *λ*<sub>*c*</sub> *c*.

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L282"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_cotangents_from_lengths

``` python

def get_cotangents_from_lengths(
    la:Float[Array, ''], # Length of side opposite vertex a (i.e. edge bc).
    lb:Float[Array, ''], # Length of side opposite vertex b (i.e. edge ca).
    lc:Float[Array, ''], # Length of side opposite vertex c (i.e. edge ab).
)->Float[Array, '3']: # Cotangents [cot_alpha, cot_beta, cot_gamma] at vertices a, b, c.

```

*Cotangents of interior angles from side lengths.*

Uses
cot (*α*) = (*b*<sup>2</sup> + *c*<sup>2</sup> − *a*<sup>2</sup>)/(4 ⋅ area).

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L256"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_angles_from_lengths

``` python

def get_angles_from_lengths(
    la:Float[Array, ''], # Length of side opposite vertex a (i.e. edge bc).
    lb:Float[Array, ''], # Length of side opposite vertex b (i.e. edge ca).
    lc:Float[Array, ''], # Length of side opposite vertex c (i.e. edge ab).
)->Float[Array, '3']: # Angles [alpha, beta, gamma] at vertices a, b, c respectively.

```

*Interior angles from side lengths using the law of cosines.*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L233"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_triangle_area_from_lengths

``` python

def get_triangle_area_from_lengths(
    la:Float[Array, ''], # Length of side opposite vertex a (i.e. edge bc).
    lb:Float[Array, ''], # Length of side opposite vertex b (i.e. edge ca).
    lc:Float[Array, ''], # Length of side opposite vertex c (i.e. edge ab).
)->Float[Array, '']: # Area of the triangle.

```

*Triangle area from side lengths using Heron’s formula.*

``` python
# Test intrinsic functions against extrinsic (vertex-based) implementations
triangles = [
    jnp.array([[0., 0.], [1., 0.], [0., 1.]]),
    jnp.array([[0., 0.], [3., 0.], [1.5, 2.]]),
    jnp.array([[0., 0., 0.], [1., 0., 0.], [0., 1., 1.]]),
    jnp.array([[1., 2.], [4., 6.], [7., 1.]]),]

for tri in triangles:
    a, b, c = tri
    la, lb, lc = jnp.linalg.norm(b - c), jnp.linalg.norm(c - a), jnp.linalg.norm(a - b)

    # Area
    assert jnp.allclose(get_triangle_area(a, b, c),
                         get_triangle_area_from_lengths(la, lb, lc), atol=1e-10)
    # Angles
    angles_ext = jnp.stack([get_angle_between_vectors(b - a, c - a),
                            get_angle_between_vectors(a - b, c - b),
                            get_angle_between_vectors(a - c, b - c)])
    assert jnp.allclose(angles_ext, get_angles_from_lengths(la, lb, lc), atol=1e-10)

    # Cotangents
    cots_ext = jnp.stack([get_cot_between_vectors(b - a, c - a),
                          get_cot_between_vectors(a - b, c - b),
                          get_cot_between_vectors(a - c, b - c)])
    assert jnp.allclose(cots_ext, get_cotangents_from_lengths(la, lb, lc), atol=1e-10)

    # Circumcenter
    cc_ext = get_circumcenter(a, b, c)
    bary = get_circumcenter_from_lengths(la, lb, lc)
    cc_int = bary[0] * a + bary[1] * b + bary[2] * c
    assert jnp.allclose(cc_ext, cc_int, atol=1e-10)
```

### Rotation matrices and normals

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L388"
target="_blank" style="float:right; font-size:smaller">source</a>

### quaternion_to_rot_mat

``` python

def quaternion_to_rot_mat(
    q:Float[Array, '4'], # Quaternion $[w, x, y, z]$ (normalized internally).
)->Float[Array, '3 3']: # Rotation matrix.

```

*Convert unit quaternion to 3D rotation matrix.*

See
`Quaternion rotation <https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation>`\_.

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L369"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_triangle_normal

``` python

def get_triangle_normal(
    a:Float[Array, '3'], b:Float[Array, '3'], c:Float[Array, '3']
)->Float[Array, '3']: # Unit normal vector.

```

*Unit normal of triangle abc (right-hand rule on a -\> b -\> c).*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L353"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_perp_2d

``` python

def get_perp_2d(
    x:Float[Array, '... 2'], # Input vector(s).
)->Float[Array, '... 2']: # Perpendicular vector(s).

```

*90-degree clockwise rotation of a 2D vector: (*x*, *y*) → (*y*, −*x*).*

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L336"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_rot_mat

``` python

def get_rot_mat(
    theta:float, # Rotation angle in radians.
)->Float[Array, '2 2']: # Rotation matrix.

```

*2D counter-clockwise rotation matrix for angle theta.*

### Barycentric coordinates

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L409"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_barycentric_coordinates

``` python

def get_barycentric_coordinates(
    point:Float[Array, 'dim'], # Query point.
    a:Float[Array, 'dim'], b:Float[Array, 'dim'], c:Float[Array, 'dim']
)->Float[Array, '3']: # Barycentric coordinates [lambda_a, lambda_b, lambda_c] summing to 1.

```

*Barycentric coordinates of point w.r.t. triangle abc.*

Uses the area-ratio method: each coordinate is the ratio of the
sub-triangle area to the full triangle area. In 3D the point is
projected onto the triangle plane (least-squares sense).

``` python
vertices = jnp.array([[0., 0.], [0., 1.], [1., 0.]])
point1 = jnp.array([0.5, 0.2])

get_barycentric_coordinates(point1, *vertices)
```

    Array([0.3, 0.2, 0.5], dtype=float64)

``` python
vertices2 = jnp.array([[0., 0., 0.], [0., 1., 0.], [1., 0., 0.]])
point2 = jnp.array([0.5, 0.2, 0.])

get_barycentric_coordinates(point2, *vertices2)
```

    Array([0.3, 0.2, 0.5], dtype=float64)

``` python
point3 = jnp.array([0.5, 0.2, 1.])

get_barycentric_coordinates(point3, *vertices2)
```

    Array([0.3, 0.2, 0.5], dtype=float64)

### Rodrigues rotation

------------------------------------------------------------------------

<a
href="https://github.com/nikolas-claussen/triangulax/blob/main/triangulax/trigonometry.py#L440"
target="_blank" style="float:right; font-size:smaller">source</a>

### rotate_around_axis

``` python

def rotate_around_axis(
    v:Float[Array, '3'], # Vector to rotate.
    axis:Float[Array, '3'], # Unit rotation axis.
    angle:Float[Array, ''], # Rotation angle in radians.
)->Float[Array, '3']: # Rotated vector.

```

*Rotate 3D vector v by angle (radians) around unit axis using Rodrigues’
formula.*

``` python
# test: rotating x-axis by 90° around z-axis should give y-axis
x = jnp.array([1., 0., 0.])
z = jnp.array([0., 0., 1.])
result = rotate_around_axis(x, z, jnp.array(jnp.pi/2))
assert jnp.allclose(result, jnp.array([0., 1., 0.]), atol=1e-10)

# test: rotating by 0 should return the same vector
assert jnp.allclose(rotate_around_axis(x, z, jnp.array(0.)), x, atol=1e-10)

# test: rotating around the vector itself should return it
v = jnp.array([1., 2., 3.])
axis = v / jnp.linalg.norm(v)
assert jnp.allclose(rotate_around_axis(v, axis, jnp.array(1.23)), v, atol=1e-10)
```
