#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Geometry functions.
"""
import math
from typing import Tuple
from numpy import array, zeros
from numpy.linalg import solve as np_solve
import design3d as d3d
[docs]
def numpy_cross2d(x, y):
return x[..., 0] * y[..., 1] - x[..., 1] * y[..., 0]
[docs]
def euler_angles_to_transfer_matrix(psi, theta, phi):
"""
Give Transition Matrix from euler angles.
Angles in radians
"""
cpsi = math.cos(psi)
spsi = math.sin(psi)
ctheta = math.cos(theta)
stheta = math.sin(theta)
cphi = math.cos(phi)
sphi = math.sin(phi)
matrix = array([[cphi * cpsi - sphi * ctheta * spsi, -spsi * cphi - cpsi * ctheta * sphi, stheta * sphi],
[cpsi * sphi + spsi * ctheta * cphi, -sphi * spsi + cphi * ctheta * cpsi, -stheta * cphi],
[spsi * stheta, cpsi * stheta, ctheta]])
return matrix
[docs]
def transfer_matrix_to_euler_angles(r_matrix):
"""Returns the Euler angle from a transfer matrix."""
if (r_matrix[2, 2] != 1) and (r_matrix[2, 2] != -1):
theta = math.acos(r_matrix[2, 2])
psi = math.atan2(r_matrix[2, 0] / math.sin(theta), r_matrix[2, 1] / math.sin(theta))
phi = math.atan2(r_matrix[0, 2] / math.sin(theta), -r_matrix[1, 2] / math.sin(theta))
else:
phi = 0
if r_matrix[2, 2] == 1:
theta = 0
psi = math.atan2(r_matrix[1, 0], r_matrix[0, 0])
else:
theta = math.pi
psi = -math.atan2(r_matrix[1, 0], r_matrix[0, 0])
return psi, theta, phi
[docs]
def get_transfer_matrix_from_basis(basis_a, basis_b):
"""
Get the matrix of tranformation that applied to the basis A gives basis B.
"""
matrix_a = array([[basis_a.vectors[0].x, basis_a.vectors[0].y, basis_a.vectors[0].z],
[basis_a.vectors[1].x, basis_a.vectors[1].y, basis_a.vectors[1].z],
[basis_a.vectors[2].x, basis_a.vectors[2].y, basis_a.vectors[2].z]])
matrix_b = array([[basis_b.vectors[0].x, basis_b.vectors[0].y, basis_b.vectors[0].z],
[basis_b.vectors[1].x, basis_b.vectors[1].y, basis_b.vectors[1].z],
[basis_b.vectors[2].x, basis_b.vectors[2].y, basis_b.vectors[2].z]])
return np_solve(matrix_a, matrix_b)
[docs]
def direction_to_euler_angles(u: d3d.Vector3D, v=None):
"""
Returns one possibility of euler angles from a vector indicating a direction.
"""
if v is None:
v = d3d.Vector3D.random(0, 1, 0, 1, 0, 1)
u = u.copy()
u = u.unit_vector()
matrix_r = zeros((3, 3))
matrix_r[0, 0] = u.x
matrix_r[1, 0] = u.y
matrix_r[2, 0] = u.z
v = v - u.dot(v) * u
v = v.unit_vector()
w = u.cross(v)
matrix_r[0, 1] = v.y
matrix_r[1, 1] = v.y
matrix_r[2, 1] = v.y
matrix_r[0, 2] = w.z
matrix_r[1, 2] = w.z
matrix_r[2, 2] = w.z
euler = transfer_matrix_to_euler_angles(matrix_r)
return euler
[docs]
def huygens2d(Ix, Iy, Ixy, area, point1, point2):
"""
Area acts the same way as the mass in 3D.
"""
a, b = point1 - point2
return Ix + area * b**2, Iy + area * a**2, Ixy - area * a * b
[docs]
def cos_image(x1: float, x2: float) -> Tuple[float, float]:
"""
Returns the interval image of cosinus function between two values.
"""
interval_min = x1 // math.pi
interval_max = x2 // math.pi
nb_interval = interval_max - interval_min
if nb_interval >= 2:
return -1, 1
if nb_interval == 1.:
if abs(interval_min) % 2 == 0.:
# Decreasing
return -1, max(math.cos(x1), math.cos(x2))
return min(math.cos(x1), math.cos(x2)), 1
return sorted((math.cos(x1), math.cos(x2)))
[docs]
def sin_image(x1: float, x2: float) -> Tuple[float, float]:
"""
Returns the interval image of sinus function between two values.
"""
x1 = x1 - 0.5 * math.pi
x2 = x2 - 0.5 * math.pi
return cos_image(x1, x2)
[docs]
def vectors3d_angle(vector1, vector2):
"""
Computes the angle between two 3 dimensional vectors.
:param vector1: The fist 3 dimensional vector
:type vector1: :class:`design3d.Vector3D`
:param vector2: The second 3 dimensional vectors
:type vector2: :class:`design3d.Vector3D`
:return: The angle between the two vectors
:rtype: float
"""
dot_v1v2 = vector1.dot(vector2)
ratio = dot_v1v2 / (vector1.norm() * vector2.norm())
ratio = max(min(ratio, 1.0), -1.0)
theta = math.acos(ratio)
return theta
[docs]
def sin_cos_angle(u1, u2):
"""
Returns an angle between 0 and 2*PI verifying cos(theta)=u1, sin(theta)=u2.
:param u1: The value of the cosinus of the returned angle
:type u1: float
:param u2: The value of the sinus of the returned angle
:type u2: float
:return: The angle verifying the two equations
:rtype: float
"""
u1 = max(-1.0, min(u1, 1.0))
u2 = max(-1.0, min(u2, 1.0))
if u1 > 0:
if u2 >= 0:
theta = math.acos(u1)
else:
theta = d3d.TWO_PI + math.asin(u2)
else:
if u2 >= 0:
theta = math.acos(u1)
else:
theta = d3d.TWO_PI - math.acos(u1)
if math.isclose(theta, d3d.TWO_PI, abs_tol=1e-9):
return 0.
return theta
[docs]
def clockwise_interior_from_circle3d(start, end, circle):
"""
Returns the clockwise interior point between start and end on the circle.
"""
start2d = start.to_2d(plane_origin=circle.frame.origin,
x=circle.frame.u, y=circle.frame.v)
end2d = end.to_2d(plane_origin=circle.frame.origin,
x=circle.frame.u, y=circle.frame.v)
# p1 angle
u1, u2 = start2d.x / circle.radius, start2d.y / circle.radius
theta1 = sin_cos_angle(u1, u2)
# p2 angle
u3, u4 = end2d.x / circle.radius, end2d.y / circle.radius
theta2 = sin_cos_angle(u3, u4)
if theta1 > theta2:
theta3 = (theta1 + theta2) / 2
elif theta2 >= theta1:
theta3 = (theta1 + theta2) / 2 + d3d.TWO_PI / 2
else:
raise NotImplementedError
if theta3 > d3d.TWO_PI:
theta3 -= d3d.TWO_PI
interior2d = d3d.Point2D(circle.radius * math.cos(theta3),
circle.radius * math.sin(theta3))
interior3d = interior2d.to_3d(plane_origin=circle.frame.origin,
vx=circle.frame.u, vy=circle.frame.v)
return interior3d
[docs]
def offset_angle(trigo, angle_start, angle_end):
"""
Calculates the offset and angle.
Seems unused
"""
if trigo:
offset = angle_start
else:
offset = angle_end
if angle_start > angle_end:
angle = angle_start - angle_end
else:
angle = angle_end - angle_start
return offset, angle
[docs]
def angle_principal_measure(angle, min_angle=-math.pi):
"""
Returns angle between O and 2 pi.
"""
max_angle = min_angle + d3d.TWO_PI
angle = angle % d3d.TWO_PI
if math.isclose(angle, min_angle, abs_tol=1e-9):
return min_angle
if math.isclose(angle, max_angle, abs_tol=1e-9):
return max_angle
return angle
[docs]
def clockwise_angle(vector1, vector2):
"""
Return the clockwise angle in radians between vector1 and vector2.
"""
vector0 = d3d.O2D
if vector0 in (vector1, vector2):
return 0
dot_v1v2 = vector1.dot(vector2)
norm_vec_1 = vector1.norm()
norm_vec_2 = vector2.norm()
sol = dot_v1v2 / (norm_vec_1 * norm_vec_2)
cross_v1v2 = vector1.cross(vector2)
sol = max(-1.0, min(1.0, sol))
inner_angle = math.acos(sol)
if cross_v1v2 < 0:
return inner_angle
return d3d.TWO_PI - inner_angle