| 3528 | """ |
| 3529 | |
| 3530 | def calc_arrows(UVW): |
| 3531 | # get unit direction vector perpendicular to (u, v, w) |
| 3532 | x = UVW[:, 0] |
| 3533 | y = UVW[:, 1] |
| 3534 | norm = np.linalg.norm(UVW[:, :2], axis=1) |
| 3535 | x_p = np.divide(y, norm, where=norm != 0, out=np.zeros_like(x)) |
| 3536 | y_p = np.divide(-x, norm, where=norm != 0, out=np.ones_like(x)) |
| 3537 | # compute the two arrowhead direction unit vectors |
| 3538 | rangle = math.radians(15) |
| 3539 | c = math.cos(rangle) |
| 3540 | s = math.sin(rangle) |
| 3541 | # construct the rotation matrices of shape (3, 3, n) |
| 3542 | r13 = y_p * s |
| 3543 | r32 = x_p * s |
| 3544 | r12 = x_p * y_p * (1 - c) |
| 3545 | Rpos = np.array( |
| 3546 | [[c + (x_p ** 2) * (1 - c), r12, r13], |
| 3547 | [r12, c + (y_p ** 2) * (1 - c), -r32], |
| 3548 | [-r13, r32, np.full_like(x_p, c)]]) |
| 3549 | # opposite rotation negates all the sin terms |
| 3550 | Rneg = Rpos.copy() |
| 3551 | Rneg[[0, 1, 2, 2], [2, 2, 0, 1]] *= -1 |
| 3552 | # Batch n (3, 3) x (3) matrix multiplications ((3, 3, n) x (n, 3)). |
| 3553 | Rpos_vecs = np.einsum("ij...,...j->...i", Rpos, UVW) |
| 3554 | Rneg_vecs = np.einsum("ij...,...j->...i", Rneg, UVW) |
| 3555 | # Stack into (n, 2, 3) result. |
| 3556 | return np.stack([Rpos_vecs, Rneg_vecs], axis=1) |
| 3557 | |
| 3558 | had_data = self.has_data() |
| 3559 | |