-
Notifications
You must be signed in to change notification settings - Fork 110
/
dualquaternion.go
192 lines (168 loc) · 6.27 KB
/
dualquaternion.go
1
2
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package spatialmath
import (
"math"
"github.com/go-gl/mathgl/mgl64"
"github.com/golang/geo/r3"
commonpb "go.viam.com/api/common/v1"
"gonum.org/v1/gonum/num/dualquat"
"gonum.org/v1/gonum/num/quat"
)
// dualQuaternion defines functions to perform rigid dualQuaternionformations in 3D.
// If you find yourself importing gonum.org/v1/gonum/num/dualquat in some other package, you should probably be
// using these instead.
type dualQuaternion struct {
dualquat.Number
}
// newDualQuaternion returns a pointer to a new dualQuaternion object whose Quaternion is an identity Quaternion.
// Since the real part of a qual quaternion should be a unit quaternion, not all zeroes, this should be used
// instead of &dualQuaternion{}.
func newDualQuaternion() *dualQuaternion {
return &dualQuaternion{dualquat.Number{
Real: quat.Number{Real: 1},
Dual: quat.Number{},
}}
}
// newDualQuaternionFromRotation returns a pointer to a new dualQuaternion object whose rotation
// quaternion is set from a provided orientation.
func newDualQuaternionFromRotation(o Orientation) *dualQuaternion {
ov := o.OrientationVectorRadians()
// Handle the zero case
if ov.OX == 0 && ov.OY == 0 && ov.OZ == 0 {
ov.OZ = 1
}
ov.Normalize()
return &dualQuaternion{dualquat.Number{
Real: ov.ToQuat(),
Dual: quat.Number{},
}}
}
// newDualQuaternionFromDH returns a pointer to a new dualQuaternion object created from a DH parameter.
func newDualQuaternionFromDH(a, d, alpha float64) *dualQuaternion {
m := mgl64.Ident4()
m.Set(1, 1, math.Cos(alpha))
m.Set(1, 2, -1*math.Sin(alpha))
m.Set(2, 0, 0)
m.Set(2, 1, math.Sin(alpha))
m.Set(2, 2, math.Cos(alpha))
qRot := mgl64.Mat4ToQuat(m)
q := newDualQuaternion()
q.Real = quat.Number{qRot.W, qRot.X(), qRot.Y(), qRot.Z()}
q.SetTranslation(r3.Vector{a, 0, d})
return q
}
// newDualQuaternionFromProtobuf returns a pointer to a new dualQuaternion object whose rotation quaternion is set from a provided
// protobuf pose.
func newDualQuaternionFromProtobuf(pos *commonpb.Pose) *dualQuaternion {
q := newDualQuaternionFromRotation(&OrientationVectorDegrees{pos.Theta, pos.OX, pos.OY, pos.OZ})
q.SetTranslation(r3.Vector{pos.X, pos.Y, pos.Z})
return q
}
// newDualQuaternionFromPose takes any pose, checks if it is already a DQ and returns that if so, otherwise creates a
// new one.
func newDualQuaternionFromPose(p Pose) *dualQuaternion {
if q, ok := p.(*dualQuaternion); ok {
return q.Clone()
}
q := newDualQuaternionFromRotation(p.Orientation())
q.SetTranslation(p.Point())
return q
}
// newDualQuaternionFromPose takes any pose, checks if it is already a DQ and returns that if so, otherwise creates a
// new one.
func dualQuaternionFromPose(p Pose) *dualQuaternion {
if q, ok := p.(*dualQuaternion); ok {
return q
}
q := newDualQuaternionFromRotation(p.Orientation().OrientationVectorRadians())
q.SetTranslation(p.Point())
return q
}
// ToProtobuf converts a dualQuaternion to a protobuf pose.
func (q *dualQuaternion) ToProtobuf() *commonpb.Pose {
final := &commonpb.Pose{}
cartQuat := dualquat.Mul(q.Number, dualquat.Conj(q.Number))
final.X = cartQuat.Dual.Imag
final.Y = cartQuat.Dual.Jmag
final.Z = cartQuat.Dual.Kmag
poseOVD := QuatToOVD(q.Real)
final.Theta = poseOVD.Theta
final.OX = poseOVD.OX
final.OY = poseOVD.OY
final.OZ = poseOVD.OZ
return final
}
// Clone returns a dualQuaternion object identical to this one.
func (q *dualQuaternion) Clone() *dualQuaternion {
// No need for deep copies here, a dualquat.Number is primitives all the way down
return &dualQuaternion{q.Number}
}
// Point multiplies the dual quaternion by its own conjugate to give a dq where the real is the identity quat,
// and the dual is representative of real world millimeters. We then return the XYZ point on its own.
// We intentionally do not return the resulting dual quaternion, because we do not want to mix dq's representing
// transformations and ones representing pure points.
func (q *dualQuaternion) Point() r3.Vector {
tQuat := dualquat.Mul(q.Number, dualquat.Conj(q.Number)).Dual
return r3.Vector{tQuat.Imag, tQuat.Jmag, tQuat.Kmag}
}
// Orientation returns the rotation quaternion as an Orientation.
func (q *dualQuaternion) Orientation() Orientation {
return (*Quaternion)(&q.Real)
}
// SetTranslation correctly sets the translation quaternion against the rotation.
func (q *dualQuaternion) SetTranslation(pt r3.Vector) {
q.Dual = quat.Number{0, pt.X / 2, pt.Y / 2, pt.Z / 2}
q.rotate()
}
// rotate multiplies the dual part of the quaternion by the real part give the correct rotation.
func (q *dualQuaternion) rotate() {
q.Dual = quat.Mul(q.Dual, q.Real)
}
// Invert returns a dualQuaternion representing the opposite transformation. So if the input q would transform a -> b,
// then Invert(p) will transform b -> a.
func (q *dualQuaternion) Invert() Pose {
return &dualQuaternion{dualquat.ConjQuat(q.Number)}
}
// SetZ sets the z translation.
func (q *dualQuaternion) SetZ(z float64) {
q.Dual.Kmag = z
}
// Transformation multiplies the dual quat contained in this dualQuaternion by another dual quat.
func (q *dualQuaternion) Transformation(by dualquat.Number) dualquat.Number {
var newReal quat.Number
var newDual quat.Number
if vecLen := 1 / quat.Abs(by.Real); vecLen != 1 {
by.Real.Real *= vecLen
by.Real.Imag *= vecLen
by.Real.Jmag *= vecLen
by.Real.Kmag *= vecLen
}
//nolint: gocritic
if q.Real.Real == 1 {
// Since we're working with unit quaternions, if either Real is 1, then that quat is an identity quat
newReal = by.Real
} else if by.Real.Real == 1 {
newReal = q.Real
} else {
// Ensure we are multiplying by a unit dual quaternion
newReal = quat.Mul(q.Real, by.Real)
}
//nolint: gocritic
if q.Dual.Real == 0 && q.Dual.Imag == 0 && q.Dual.Jmag == 0 && q.Dual.Kmag == 0 {
newDual = quat.Mul(q.Real, by.Dual)
} else if by.Dual.Real == 0 && by.Dual.Imag == 0 && by.Dual.Jmag == 0 && by.Dual.Kmag == 0 {
newDual = quat.Mul(q.Dual, by.Real)
} else {
newDual = quat.Add(quat.Mul(q.Real, by.Dual), quat.Mul(q.Dual, by.Real))
}
return dualquat.Number{
Real: newReal,
Dual: newDual,
}
}
// OffsetBy takes two offsets and computes the final position.
func OffsetBy(a, b *commonpb.Pose) *commonpb.Pose {
q1 := newDualQuaternionFromProtobuf(a)
q2 := newDualQuaternionFromProtobuf(b)
q3 := &dualQuaternion{q1.Transformation(q2.Number)}
return q3.ToProtobuf()
}