from typing import List, Tuple, Union, SupportsFloat, Optional, Sequence
from enum import IntEnum
from webcface.typing import convertible_to_float, is_float_sequence
import webcface.transform_impl
[docs]
class Point:
"""3次元or2次元の座標
手動でコンストラクタを呼んでもいいが、
PointをうけとるAPIは基本的に Sequence[SupportsFloat] を受け付け、
内部でPointに変換される。
"""
_x: float
_y: float
_z: float
def __init__(
self,
pos: Sequence[SupportsFloat],
) -> None:
"""座標を初期化
:arg pos: 座標
2次元の場合 :code:`[float, float]`,
3次元の場合 :code:`[float, float, float]` など
"""
assert len(pos) in (2, 3), "Point must be (x, y) or (x, y, z), got " + str(pos)
self._x = float(pos[0])
self._y = float(pos[1])
self._z = float(pos[2]) if len(pos) == 3 else 0.0
@property
def pos(self) -> Tuple[float, float, float]:
"""座標を返す
2次元の場合は pos[0:2] を使う
"""
return (self._x, self._y, self._z)
@pos.setter
def pos(self, new_pos: Sequence[SupportsFloat]) -> None:
"""座標をセット
mypyが型に関してエラーを出す場合はset_pos()を使うと良いかも
"""
self.set_pos(new_pos)
[docs]
def set_pos(self, pos: Sequence[SupportsFloat]) -> None:
"""座標をセット
:arg pos: 座標
2次元の場合 :code:`[float, float]`,
3次元の場合 :code:`[float, float, float]` など
"""
assert len(pos) in (2, 3), "Point must be (x, y) or (x, y, z), got " + str(pos)
self._x = float(pos[0])
self._y = float(pos[1])
self._z = float(pos[2]) if len(pos) == 3 else 0.0
def __eq__(self, other: object) -> bool:
"""Pointと比較した場合座標の差が 1e-8 未満ならTrue
(ver3.0〜) Transformとは比較できない
"""
if isinstance(other, Transform):
raise TypeError("Transform can't be compared with Point")
elif isinstance(other, Point):
return (
abs(self._x - other._x) < 1e-8
and abs(self._y - other._y) < 1e-8
and abs(self._z - other._z) < 1e-8
)
else:
return NotImplemented
def __ne__(self, other: object) -> bool:
return not self == other
def __add__(self, other: "Point") -> "Point":
if isinstance(other, (Transform, Rotation)):
raise TypeError("Transform or Rotation can't be added to Point")
if isinstance(other, Point):
return Point([a + b for a, b in zip(self.pos, other.pos)])
return NotImplemented
def __iadd__(self, other: "Point") -> "Point":
if isinstance(other, (Transform, Rotation)):
raise TypeError("Transform or Rotation can't be added to Point")
if isinstance(other, Point):
self.set_pos([a + b for a, b in zip(self.pos, other.pos)])
return self
return NotImplemented
def __sub__(self, other: "Point") -> "Point":
if isinstance(other, (Transform, Rotation)):
raise TypeError("Transform or Rotation can't be subtracted from Point")
if isinstance(other, Point):
return Point([a - b for a, b in zip(self.pos, other.pos)])
return NotImplemented
def __isub__(self, other: "Point") -> "Point":
if isinstance(other, (Transform, Rotation)):
raise TypeError("Transform or Rotation can't be subtracted to Point")
if isinstance(other, Point):
self.set_pos([a - b for a, b in zip(self.pos, other.pos)])
return self
return NotImplemented
def __neg__(self) -> "Point":
return Point([-a for a in self.pos])
def __pos__(self) -> "Point":
return Point(self.pos)
def __mul__(self, other: SupportsFloat) -> "Point":
if isinstance(other, (Transform, Rotation, Point)):
raise TypeError(
"Transform, Rotation or Point can't be multiplied to Point from right"
)
return Point([a * float(other) for a in self.pos])
def __rmul__(self, other: SupportsFloat) -> "Point":
if isinstance(other, (Transform, Rotation, Point)):
return NotImplemented # should be defined in the other class
return Point([a * float(other) for a in self.pos])
def __imul__(self, other: SupportsFloat) -> "Point":
if isinstance(other, (Transform, Rotation, Point)):
raise TypeError(
"Transform, Rotation or Point can't be multiplied to Point from right"
)
self.set_pos([a * float(other) for a in self.pos])
return self
def __div__(self, other: SupportsFloat) -> "Point":
if isinstance(other, (Transform, Rotation, Point)):
raise TypeError("Transform, Rotation or Point can't be divided from Point")
return Point([a / float(other) for a in self.pos])
def __idiv__(self, other: SupportsFloat) -> "Point":
if isinstance(other, (Transform, Rotation, Point)):
raise TypeError("Transform, Rotation or Point can't be multiplied to Point")
self.set_pos([a / float(other) for a in self.pos])
return self
[docs]
class AxisSequence(IntEnum):
"""オイラー角の回転順序 (ver3.0〜)
* 右手系の座標系で、
内的回転(intrinsic rotation)でz軸,y軸,x軸の順に回転させる系
= 外的回転(extrinsic rotation)でX軸,Y軸,Z軸の順に回転させる系
= 回転行列がZ(α)Y(β)X(γ)と表される系
を、 AxisSequence::ZYX と表記する。
* ver2.3までの実装はすべてZYXで、現在もWebCFaceの内部表現は基本的にZYXの系である。
* またWebCFaceのインタフェースでオイラー角の回転角を指定する場合、
軸の指定順は内的回転を指す。(AxisSequenceにおける左から右の並び順と一致。)
"""
ZXZ = 0
XYX = 1
YZY = 2
ZYZ = 3
XZX = 4
YXY = 5
XYZ = 6
YZX = 7
ZXY = 8
XZY = 9
ZYX = 10
YXZ = 11
[docs]
class Rotation:
"""3次元の回転 (ver3.0〜)
* 内部ではz-y-x系のオイラー角または3x3回転行列で保持している。
* 送受信時にはすべてこのzyxのオイラー角に変換される。
* 2次元の回転を表すのにも使われ、
その場合オイラー角 rot() の最初の要素(=z軸周りの回転)を使って回転を表し、
残りの要素(x,y軸周りの回転)を0とする。
"""
_az: Optional[float]
_ay: Optional[float]
_ax: Optional[float]
_rmat: Optional[
Tuple[
Tuple[float, float, float],
Tuple[float, float, float],
Tuple[float, float, float],
]
]
def __init__(self, az, ay, ax, rmat):
"""このコンストラクタではなく、rot_from_* 関数を使うこと"""
if az is None and ay is None and ax is None:
assert rmat is not None
self._rmat = tuple(tuple(float(v) for v in r) for r in rmat)
self._az = None
self._ay = None
self._ax = None
else:
assert rmat is None
assert az is not None and ay is not None and ax is not None
self._az = float(az)
self._ay = float(ay)
self._ax = float(ax)
self._rmat = None
@property
def rot(self) -> Tuple[float, float, float]:
"""回転角を取得
2次元の場合は rot[0] を使う
.. deprecated:: ver3.0
"""
return self.rot_euler()
[docs]
def rot_2d(self) -> float:
"""2次元の回転角を取得 (ver3.0〜)"""
return self.rot_euler()[0]
[docs]
def rot_euler(self, axis=AxisSequence.ZYX) -> Tuple[float, float, float]:
"""回転角をオイラー角として取得 (ver3.0〜)
:arg axis: オイラー角の回転順序
"""
if axis == AxisSequence.ZYX:
if self._az is None or self._ay is None or self._ax is None:
assert self._rmat is not None
(self._az, self._ay, self._ax) = (
webcface.transform_impl.matrix_to_euler(self._rmat, axis)
)
return (self._az, self._ay, self._ax)
else:
return webcface.transform_impl.matrix_to_euler(self.rot_matrix(), axis)
[docs]
def rot_matrix(
self,
) -> Tuple[
Tuple[float, float, float],
Tuple[float, float, float],
Tuple[float, float, float],
]:
"""回転角を回転行列として取得 (ver3.0〜)"""
if self._rmat is None:
assert (
self._az is not None and self._ay is not None and self._ax is not None
)
self._rmat = webcface.transform_impl.euler_to_matrix(
(self._az, self._ay, self._ax), AxisSequence.ZYX
)
return self._rmat
[docs]
def rot_quat(self) -> Tuple[float, float, float, float]:
"""回転角をクォータニオン(w, x, y, z)として取得 (ver3.0〜)"""
return webcface.transform_impl.matrix_to_quaternion(self.rot_matrix())
[docs]
def rot_axis_angle(self) -> Tuple[Tuple[float, float, float], float]:
"""回転角を軸と角度((x, y, z), angle)として取得 (ver3.0〜)"""
return webcface.transform_impl.quaternion_to_axis_angle(self.rot_quat())
def __eq__(self, other: object) -> bool:
"""Rotationと比較した場合回転行列の各要素の差が 1e-8 未満ならTrue
Transformとも比較できる
"""
if isinstance(other, Transform):
return other == Transform(self)
elif isinstance(other, Rotation):
return all(
all(abs(a - b) < 1e-8 for a, b in zip(ra, rb))
for ra, rb in zip(self.rot_matrix(), other.rot_matrix())
)
else:
return NotImplemented
def __ne__(self, other: object) -> bool:
return not self == other
[docs]
def applied_to_point(
self, other: Union["Point", Sequence[SupportsFloat]]
) -> "Point":
"""Point を回転させた結果を返す
:arg other: 回転させる対象
"""
if not isinstance(other, Point):
other = Point(other)
return Point(
webcface.transform_impl.apply_rot_point(self.rot_matrix(), other.pos)
)
[docs]
def applied_to_rotation(self, other: "Rotation") -> "Rotation":
"""Rotation を回転させた結果を返す
:arg other: 回転させる対象
"""
return Rotation(
None,
None,
None,
webcface.transform_impl.apply_rot_rot(
self.rot_matrix(), other.rot_matrix()
),
)
def __mul__(
self, other: Union["Point", "Rotation", "Transform"]
) -> Union["Point", "Rotation", "Transform"]:
if isinstance(other, Transform):
return self.applied_to_transform(other)
if isinstance(other, Point):
return self.applied_to_point(other)
if isinstance(other, Rotation):
return self.applied_to_rotation(other)
return NotImplemented
def __imul__(self, other: "Rotation") -> "Rotation":
if isinstance(other, Transform):
raise TypeError("Rotation * Transform is not Rotation")
elif isinstance(other, Point):
raise TypeError("Rotation * Point is not Rotation")
elif isinstance(other, Rotation):
result = self.applied_to_rotation(other)
else:
return TypeError("Rotation can't be multiplied to " + str(type(other)))
self._az = None
self._ay = None
self._ax = None
self._rmat = result._rmat
return self
[docs]
def inversed(self) -> "Rotation":
"""逆回転を取得"""
return Rotation(
None,
None,
None,
webcface.transform_impl.inverse_matrix(self.rot_matrix()),
)
[docs]
def rot_from_euler(
angles: Sequence[SupportsFloat], axis=AxisSequence.ZYX
) -> "Rotation":
"""オイラー角からRotationを作成
:arg angles: オイラー角
:arg axis: オイラー角の回転順序
"""
assert len(angles) == 3, "Euler angle must be 3 dimensional, got " + str(angles)
if axis == AxisSequence.ZYX:
return Rotation(angles[0], angles[1], angles[2], None)
else:
return Rotation(
None, None, None, webcface.transform_impl.euler_to_matrix(angles, axis)
)
[docs]
def rot_from_matrix(rmat: Sequence[Sequence[SupportsFloat]]) -> "Rotation":
"""回転行列からRotationを作成
:arg rmat: 回転行列
"""
assert len(rmat) == 3, "Rotation matrix must be 3x3, got " + str(rmat)
assert all(len(r) == 3 for r in rmat), "Rotation matrix must be 3x3, got " + str(
rmat
)
return Rotation(None, None, None, rmat)
[docs]
def rot_from_quat(quat: Sequence[SupportsFloat]) -> "Rotation":
"""クォータニオンからRotationを作成
:arg quat: クォータニオン (w, x, y, z)
"""
assert len(quat) == 4, "Quaternion must be 4 dimensional, got " + str(quat)
return rot_from_matrix(webcface.transform_impl.quaternion_to_matrix(quat))
[docs]
def rot_from_axis_angle(
axis: Sequence[SupportsFloat], angle: SupportsFloat
) -> "Rotation":
"""軸と角度からRotationを作成
:arg axis: 軸
:arg angle: 角度
"""
assert len(axis) == 3, "Axis must be 3 dimensional, got " + str(axis)
return rot_from_quat(webcface.transform_impl.axis_angle_to_quaternion(axis, angle))
[docs]
def rot_2d(angle: SupportsFloat) -> "Rotation":
"""2次元の回転を作成 (ver3.0〜)
rot_z() と同じ。
:arg angle: 回転角
"""
return Rotation(angle, 0, 0, None)
[docs]
def rot_z(angle: SupportsFloat) -> "Rotation":
"""z軸周りの回転を作成 (ver3.0〜)
:arg angle: 回転角
"""
return Rotation(angle, 0, 0, None)
[docs]
def rot_y(angle: SupportsFloat) -> "Rotation":
"""y軸周りの回転を作成 (ver3.0〜)
:arg angle: 回転角
"""
return Rotation(0, angle, 0, None)
[docs]
def rot_x(angle: SupportsFloat) -> "Rotation":
"""x軸周りの回転を作成 (ver3.0〜)
:arg angle: 回転角
"""
return Rotation(0, 0, angle, None)
[docs]
def identity() -> "Transform":
"""なにもしないTransformを作成"""
return Transform([0, 0, 0], rot_from_euler([0, 0, 0]))
[docs]
def translation(pos: Union["Point", Sequence[SupportsFloat]]) -> "Transform":
"""平行移動のみをするTransformを作成 (ver3.0〜)"""
return Transform(pos, rot_from_euler([0, 0, 0]))