Last active
April 29, 2024 22:49
-
-
Save awesomebytes/7ccbd396511db71d0a51341569fa95fa to your computer and use it in GitHub Desktop.
Get a quaternion representing the orientation of the plane given by three points in space.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import numpy as np | |
import math | |
from tf.transformations import euler_from_quaternion, quaternion_from_euler | |
from geometry_msgs.msg import Point | |
""" | |
Stuff to compute an orientation quaternion | |
from three points in the space (that represent a plane, | |
the quaternion represents the orientation of the plane). | |
Author: Sammy Pfeiffer <Sammy.Pfeiffer@student.uts.edu.au> | |
""" | |
def normalize(v): | |
norm = np.linalg.norm(v) | |
if norm == 0: | |
return v | |
return v / norm | |
# http://stackoverflow.com/questions/17044296/quaternion-rotation-without-euler-angles | |
def vectors_from_three_points(p1, p2, p3): | |
""" | |
Given 3 points in the space, return the vectors | |
p1->p2 and p1->p3. | |
""" | |
if type(p1) == Point: | |
x0, y0, z0 = p1.x, p1.y, p1.z | |
elif type(p1) == list: | |
x0, y0, z0 = p1[0], p1[1], p1[2] | |
if type(p2) == Point: | |
x1, y1, z1 = p2.x, p2.y, p2.z | |
elif type(p2) == list: | |
x1, y1, z1 = p2[0], p2[1], p2[2] | |
if type(p3) == Point: | |
x2, y2, z2 = p3.x, p3.y, p3.z | |
elif type(p3) == list: | |
x0, y0, z0 = p3[0], p3[1], p3[2] | |
v0 = [x1 - x0, y1 - y0, z1 - z0] | |
v1 = [x2 - x0, y2 - y0, z2 - z0] | |
return v0, v1 | |
def orthogonal_vectors_from_vectors(v0, v1): | |
""" | |
Given two vectors that arent colinear return | |
v0, the orthogonal vector to v0 and the crossproduct, | |
and the crossproduct vector of v0 and v1. | |
""" | |
# Two vectors v0 |/ v1 | |
v0 = normalize(v0) | |
v1 = normalize(v1) | |
# The cross product that points up |./ | |
vc = np.cross(v0, v1) | |
# The orthogonal vector to the original v0 and to the crossproduct |._ | |
v0c = np.cross(v0, vc) | |
return v0, v0c, vc | |
def quaternion_from_three_points(p1, p2, p3): | |
""" | |
Given three points p1, p2, p3 that form a plane (cant be colinear) | |
return the quaternion that represents the orientation | |
of the normal of the plane. | |
The orientation of the quaternion aligns: | |
Z axis with the crossproduct (vc) of the vector p1->p2 (v0) and p1->p3 (v1) 'up' | |
Y axis with the vector p1->p2 (v0) | |
X axis with the orthogonal vector to the vector p1->p2 (v0) and the crossproduct (vc) | |
:param p1 Point or list: Point or list(x, y, z) | |
:param p2 Point or list: Point or list(x, y, z) | |
:param p3 Point or list: Point or list(x, y, z) | |
""" | |
v0, v1 = vectors_from_three_points(p1, p2, p3) | |
# original is p1->p2, orthogonal is orthog to p1->p2 and to up, | |
# up is cross product of p1->p2 and p1->p3 | |
original, orthogonal, up = orthogonal_vectors_from_vectors(v0, v1) | |
# Align X to v0 and Z to vc | |
q = quaternion_from_forward_and_up_vectors(up, original) | |
return q | |
def quaternion_from_forward_and_up_vectors(forward, up): | |
""" | |
Inspired in the Unity LookRotation Quaternion function, | |
https://answers.unity.com/questions/467614/what-is-the-source-code-of-quaternionlookrotation.html | |
this returns a quaternion from two orthogonal vectors (x, y, z) | |
representing forward and up. | |
""" | |
v0 = normalize(forward) | |
v1 = normalize(np.cross(normalize(up), v0)) | |
v2 = np.cross(v0, v1) | |
m00, m01, m02 = v1 | |
m10, m11, m12 = v2 | |
m20, m21, m22 = v0 | |
num8 = (m00 + m11) + m22 | |
if num8 > 0.0: | |
num = math.sqrt(num8 + 1.0) | |
w = num * 0.5 | |
num = 0.5 / num | |
x = (m12 - m21) * num | |
y = (m20 - m02) * num | |
z = (m01 - m10) * num | |
return x, y, z, w | |
if (m00 >= m11) and (m00 >= m22): | |
num7 = math.sqrt(((1.0 + m00) - m11) - m22) | |
num4 = 0.5 / num7 | |
x = 0.5 * num7 | |
y = (m01 + m10) * num4 | |
z = (m02 + m20) * num4 | |
w = (m12 - m21) * num4 | |
return x, y, z, w | |
if m11 > m22: | |
num6 = math.sqrt(((1.0 + m11) - m00) - m22) | |
num3 = 0.5 / num6 | |
x = (m10 + m01) * num3 | |
y = 0.5 * num6 | |
z = (m21 + m12) * num3 | |
w = (m20 - m02) * num3 | |
return x, y, z, w | |
num5 = math.sqrt(((1.0 + m22) - m00) - m11) | |
num2 = 0.5 / num5 | |
x = (m20 + m02) * num2 | |
y = (m21 + m12) * num2 | |
z = 0.5 * num5 | |
w = (m01 - m10) * num2 | |
return x, y, z, w | |
if __name__ == '__main__': | |
# Tests with Rviz | |
import rospy | |
# Add to your workspace: https://github.com/DavidB-CMU/rviz_tools_py | |
from rviz_tools_py import RvizMarkers | |
from visualization_msgs.msg import Marker | |
from geometry_msgs.msg import Polygon, Point32, Pose, Quaternion | |
import random | |
rospy.init_node('teststuff') | |
rm = RvizMarkers('base_link', 'teststuff') | |
rospy.sleep(1.0) | |
def visualize(v1, v2, v3, rm): | |
p1 = Point(*v1) | |
p2 = Point(*v2) | |
p3 = Point(*v3) | |
rm.publishSphere(p1, 'red', 0.1, None) | |
rm.publishText(p1, 'p1', 'red', 0.2, None) | |
rm.publishSphere(p2, 'red', 0.1, None) | |
rm.publishText(p2, 'p2', 'red', 0.2, None) | |
rm.publishSphere(p3, 'red', 0.1, None) | |
rm.publishText(p3, 'p3', 'red', 0.2, None) | |
pol = Polygon() | |
pol.points.append(Point32(*v1)) | |
pol.points.append(Point32(*v2)) | |
pol.points.append(Point32(*v3)) | |
rm.publishPolygon(pol, 'pink', 0.01, None) | |
v0, v1 = vectors_from_three_points(p1, p2, p3) | |
# Create an arrow from start and end points | |
m = Marker() | |
m.header.frame_id = 'base_link' | |
m.type = m.ARROW | |
m.action = m.ADD | |
# v0 | |
m.points.append(p1) | |
m.points.append(p2) | |
m.color.a = 0.3 | |
m.color.g = 1.0 | |
m.scale.x = 0.05 | |
m.scale.y = 0.1 | |
m.scale.z = 0.0 | |
m.id = 9999 + random.randint(1, 100000) | |
m.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(m) | |
v0p = Point() | |
v0p.x = p1.x + p2.x / 2.0 | |
v0p.y = p1.y + p2.y / 2.0 | |
v0p.z = p1.z + p2.z / 2.0 | |
rm.publishText(v0p, 'v0', 'green', 0.2, None) | |
m2 = Marker() | |
m2.header.frame_id = 'base_link' | |
m2.type = m2.ARROW | |
m2.action = m2.ADD | |
# v1 | |
m2.points.append(p1) | |
m2.points.append(p3) | |
m2.color.a = 0.3 | |
m2.color.g = 1.0 | |
m2.scale.x = 0.05 | |
m2.scale.y = 0.1 | |
m2.scale.z = 0.0 | |
m2.id = 999 + random.randint(1, 100000) | |
m2.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(m2) | |
v1p = Point() | |
v1p.x = p1.x + p3.x / 2.0 | |
v1p.y = p1.y + p3.y / 2.0 | |
v1p.z = p1.z + p3.z / 2.0 | |
rm.publishText(v1p, 'v1', 'green', 0.2, None) | |
# Cross product vector | |
c = np.cross(v0, v1) | |
pc = Point(*c) | |
m3 = Marker() | |
m3.header.frame_id = 'base_link' | |
m3.type = m3.ARROW | |
m3.action = m3.ADD | |
# v1 | |
m3.points.append(p1) | |
m3.points.append(pc) | |
m3.color.a = 0.3 | |
m3.color.g = 1.0 | |
m3.scale.x = 0.05 | |
m3.scale.y = 0.1 | |
m3.scale.z = 0.0 | |
m3.id = 9999 | |
m3.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(m3) | |
vc = Point() | |
vc.x = p1.x + pc.x / 2.0 | |
vc.y = p1.y + pc.y / 2.0 | |
vc.z = p1.z + pc.z / 2.0 | |
rm.publishText(vc, 'vc', 'green', 0.2, None) | |
original, orthogonal, up = orthogonal_vectors_from_vectors(v0, v1) | |
mforward = Marker() | |
mforward.header.frame_id = 'base_link' | |
mforward.type = mforward.ARROW | |
mforward.action = mforward.ADD | |
# forward vector | |
mforward.points.append(p1) | |
pforward = Point(*original) | |
mforward.points.append(pforward) | |
mforward.color.a = 0.4 | |
mforward.color.g = 0.5 | |
mforward.color.r = 0.5 | |
mforward.scale.x = 0.08 | |
mforward.scale.y = 0.1 | |
mforward.scale.z = 0.0 | |
mforward.id = 9994 + random.randint(1, 100000) | |
mforward.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(mforward) | |
vforp = Point() | |
vforp.x = p1.x + pforward.x / 2.0 | |
vforp.y = p1.y + pforward.y / 2.0 | |
vforp.z = p1.z + pforward.z / 2.0 | |
rm.publishText(vforp, 'original', 'orange', 0.2, None) | |
mup = Marker() | |
mup.header.frame_id = 'base_link' | |
mup.type = mup.ARROW | |
mup.action = mup.ADD | |
# forward vector | |
mup.points.append(p1) | |
pup = Point(*up) | |
mup.points.append(pup) | |
mup.color.a = 0.4 | |
mup.color.g = 0.5 | |
mup.color.r = 0.5 | |
mup.scale.x = 0.08 | |
mup.scale.y = 0.1 | |
mup.scale.z = 0.0 | |
mup.id = 9994 + random.randint(1, 100000) | |
mup.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(mup) | |
vupp = Point() | |
vupp.x = p1.x + pup.x / 2.0 | |
vupp.y = p1.y + pup.y / 2.0 | |
vupp.z = p1.z + pup.z / 2.0 | |
rm.publishText(vupp, 'up', 'orange', 0.2, None) | |
mortho = Marker() | |
mortho.header.frame_id = 'base_link' | |
mortho.type = mortho.ARROW | |
mortho.action = mortho.ADD | |
# forward vector | |
mortho.points.append(p1) | |
portho = Point(*orthogonal) | |
mortho.points.append(portho) | |
mortho.color.a = 0.4 | |
mortho.color.g = 0.5 | |
mortho.color.r = 0.5 | |
mortho.scale.x = 0.08 | |
mortho.scale.y = 0.1 | |
mortho.scale.z = 0.0 | |
mortho.id = 99494 + random.randint(1, 100000) | |
mortho.lifetime = rospy.Duration(100.0) | |
rm.publishMarker(mortho) | |
vorthop = Point() | |
vorthop.x = p1.x + portho.x / 2.0 | |
vorthop.y = p1.y + portho.y / 2.0 | |
vorthop.z = p1.z + portho.z / 2.0 | |
rm.publishText(vorthop, 'orthogonal', 'orange', 0.2, None) | |
# Align X to vc and Y to v0 | |
# q = quaternion_from_forward_and_up_vectors(original, orthogonal) | |
# roll, pitch, yaw = euler_from_quaternion(q) | |
# q = quaternion_from_euler(roll + math.radians(90), pitch, yaw) | |
# Align X to v0 and Y to __, Z to vc | |
q = quaternion_from_forward_and_up_vectors(up, original) | |
# roll, pitch, yaw = euler_from_quaternion(q) | |
# q = quaternion_from_euler(roll, pitch, yaw + math.radians(90)) | |
final_pose = Pose(p1, Quaternion(*q)) | |
rm.publishAxis(final_pose, 0.3, 0.05, None) | |
rm.deleteAllMarkers() | |
rospy.sleep(0.4) | |
# v1 = [0.0, 0.0, 0.0] | |
# v2 = [1.0, 0.0, 0.0] | |
# v3 = [0.0, 1.0, 0.0] | |
# visualize(v1, v2, v3, rm) | |
# v1 = [0.0, 0.0, 0.0] | |
# v2 = [0.0, 1.0, 0.0] | |
# v3 = [0.0, 0.0, 1.0] | |
# visualize(v1, v2, v3, rm) | |
# v1 = [0.0, 0.0, 0.0] | |
# v2 = [1.0, 1.0, 0.0] | |
# v3 = [1.0, 0.0, 0.0] | |
# visualize(v1, v2, v3, rm) | |
# v1 = [0.0, 0.0, 0.0] | |
# v2 = [1.0, 1.0, 0.0] | |
# v3 = [1.0, 0.0, 1.0] | |
# visualize(v1, v2, v3, rm) | |
# v1 = [0.0, 0.0, 0.0] | |
# v2 = [1.0, 0.0, 0.0] | |
# v3 = [0.0, 1.0, 1.0] | |
# visualize(v1, v2, v3, rm) | |
v1 = [0.0, 0.0, 0.0] | |
v2 = [1.0, 0.5, 0.3] | |
v3 = [0.3, 0.4, 1.0] | |
visualize(v1, v2, v3, rm) | |
rospy.spin() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you, that is very useful!