Skip to content

tip_angles_intrinsic

tip_angles_intrinsic(l_sq, F, use_small_angle_approx=True)

Computes the angles formed by each vertex within its respective face (the tip angle) using only intrinsic information (squared halfedge edge lengths).

Parameters:

Name Type Description Default
l_sq (m,3) numpy array

squared halfedge lengths as computed by halfedge_lengths_squared

required
F (m,3) numpy int array

face index list of a triangle mesh

required
use_small_angle_approx bool, optional (default

If True, uses a different, more more stable formula for small angles.

True

Returns:

Type Description

tip angles for each vertex referenced in F

Examples:

# Random lengths
c = np.random.default_rng().random() + 0.1
l_sq = c * np.array([[1., 1., 1.]])
# Triangle faces
f = np.array([[0,1,2]],dtype=int)
# Get tip angles
alpha = gpy.tip_angles_intrinsic(l_sq,f)
Source code in src/gpytoolbox/tip_angles_intrinsic.py
 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def tip_angles_intrinsic(l_sq, F,
    use_small_angle_approx=True):
    """Computes the angles formed by each vertex within its respective face
    (the tip angle) using only intrinsic information (squared halfedge edge
    lengths).

    Parameters
    ----------
    l_sq : (m,3) numpy array
        squared halfedge lengths as computed by halfedge_lengths_squared
    F : (m,3) numpy int array
        face index list of a triangle mesh
    use_small_angle_approx : bool, optional (default: True)
        If True, uses a different, more more stable formula for small angles.

    Returns
    -------
    α : (m,3) numpy array
        tip angles for each vertex referenced in `F`

    Examples
    --------
    ```python
    # Random lengths
    c = np.random.default_rng().random() + 0.1
    l_sq = c * np.array([[1., 1., 1.]])
    # Triangle faces
    f = np.array([[0,1,2]],dtype=int)
    # Get tip angles
    alpha = gpy.tip_angles_intrinsic(l_sq,f)
    ```

    """


    assert F.shape[1] == 3
    assert l_sq.shape == F.shape
    assert np.all(l_sq>=0)

    #Using cosine rule
    def cr(a, b, c):
        cosgamma = (a+b-c) / (2.*np.sqrt(a*b))
        gamma = np.arccos(np.clip(cosgamma, -1., 1.))
        if use_small_angle_approx:
            #If c is very small, expect numerical error and use a series approx.
            small = np.sqrt(np.finfo(c.dtype).eps) #This is quite aggressive, can also try without sqrt
            i = c<small
            if np.any(i):
                # https://www.nayuki.io/page/numerically-stable-law-of-cosines
                sqrt_ai, sqrt_bi = np.sqrt(a[i]), np.sqrt(b[i])
                gsq = (c[i] - (sqrt_ai-sqrt_bi)**2) / (sqrt_ai*sqrt_bi)
                gamma[i] = np.sqrt(np.clip(gsq, 0., None))
        return gamma
    alpha = np.stack(
        [cr(l_sq[:,1], l_sq[:,2], l_sq[:,0]),
        cr(l_sq[:,2], l_sq[:,0], l_sq[:,1]),
        cr(l_sq[:,0], l_sq[:,1], l_sq[:,2])],
        axis=-1)

    return alpha