homogeneous_transformation 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ log.org
20
+ tester.rb
21
+ *~
22
+ coverage/*
23
+ doc/*
24
+ other/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in homogeneous_transformation.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2016, Cory Crean
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * The name(s) of the author(s) may not be used to endorse or promote
12
+ products derived from this software without specific prior written
13
+ permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CORY CREAN BE
19
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
25
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # HomogeneousTransformation
2
+
3
+ Homogeneous transformations are common in robotics, where each of the
4
+ bodies that make up the robot has a coordinate frame associated with
5
+ it. In general, the coordinate frames for two different bodies will
6
+ have different orientations and different locations in space. If you
7
+ have a vector that describes the location of a point relative to one
8
+ reference frame, the question arises of how to find a vector that
9
+ describes the location of the same point relative to a different
10
+ reference frame. A homogeneous transformation provides a
11
+ straightforward way to perform the conversion. See the usage
12
+ instructions below for more details.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'homogeneous_transformation'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install homogeneous_transformation
27
+
28
+ ## Usage
29
+
30
+ To use the HomogeneousTransformation class in your program, include
31
+ the following line:
32
+
33
+ ```
34
+ require 'homogeneous_transformation'
35
+ ```
36
+
37
+ The following typedef has bee provided for your convenience:
38
+
39
+ ```
40
+ HomTran = HomogeneousTransformation
41
+ ```
42
+
43
+ We will use it throughout the remainder of this readme for the sake of
44
+ brevity.
45
+
46
+ In order to initialize a HomogeneousTransformation, you need to create
47
+ a UnitQuaternion object that represents the HomTran's orientation, and
48
+ you need to provide a vector that gives the HomTran's location in
49
+ space. The [unit_quaternion
50
+ gem](https://rubygems.org/gems/unit_quaternion) is required
51
+ automatically when you require the homogeneous_transformation gem.
52
+
53
+ ```
54
+ q = UnitQuaternion.fromAngleAxis(Math::PI/2, Vector[1,1,1])
55
+ t = Vector[1,2,3]
56
+ frame = HomTran.new(q, t)
57
+ => #<HomogeneousTransformation:0x00000002595a68 @q=(0.7071067811865476, Vector[0.408248290463863, 0.408248290463863, 0.408248290463863]), @t=Vector[1, 2, 3]>
58
+ ```
59
+
60
+ You can recover the orientation or translation for a given HomTran
61
+ object using the following methods:
62
+
63
+ ```
64
+ frame.getQuaternion()
65
+ => (0.7071067811865476, Vector[0.408248290463863, 0.408248290463863, 0.408248290463863])
66
+
67
+ frame.getTranslation()
68
+ => Vector[1, 2, 3]
69
+ ```
70
+
71
+ You can get the 4x4 matrix representation of the HomTran as follows:
72
+
73
+ ```
74
+ frame.getMatrix()
75
+ => Matrix[[0.3333333333333335, -0.24401693585629253, 0.9106836025229592, 1], [0.9106836025229592, 0.3333333333333335, -0.24401693585629253, 2], [-0.24401693585629253, 0.9106836025229592, 0.3333333333333335, 3], [0, 0, 0, 1]]
76
+ ```
77
+
78
+ You can also set the orientation, translation, or matrix
79
+ representation for an existing HomTran object using the setQuaternion,
80
+ setTranslation, and setMatrix methods.
81
+
82
+ Now for the useful part. If you have a HomTran object that describes
83
+ the location of a child frame relative to its parent frame, and you
84
+ have a vector expressed in the child frame, you can get the
85
+ corresponding vector in the parent frame using the transform method:
86
+
87
+ ```
88
+ v = Vector[1, 2, 3]
89
+ frame.transform(v)
90
+ => Vector[3.577350269189626, 2.8452994616207485, 5.577350269189626]
91
+ ```
92
+
93
+ If you want the inverse of a HomTran, for example, to transform a
94
+ vector from its representation in the parent frame to its
95
+ representation in the child frame, you can use the inverse method:
96
+
97
+ ```
98
+ frame.inverse()
99
+ => #<HomogeneousTransformation:0x000000025a7a10 @q=(0.7071067811865476, Vector[-0.408248290463863, -0.408248290463863, -0.408248290463863]), @t=Vector[-1.4226497308103743, -3.154700538379252, -1.4226497308103747]>
100
+ ```
101
+
102
+ Finally, to compose two transformations described by HomTran objects,
103
+ use the multiplication operator:
104
+
105
+ ```
106
+ frame2 = HomTran.new(UnitQuaternion.new(1,2,3,4), Vector[1, 0, 0])
107
+ => #<HomogeneousTransformation:0x000000025bc820 @q=(0.18257418583505536, Vector[0.3651483716701107, 0.5477225575051661, 0.7302967433402214]), @t=Vector[1, 0, 0]>
108
+
109
+ frame2 * frame
110
+ #<HomogeneousTransformation:0x00000002334648 @q=(-0.5417209483763564, Vector[0.25819888974716115, 0.6109051323707207, 0.5163977794943223]), @t=Vector[2.7999999999999994, 2.0, 2.5999999999999996]>
111
+ ```
112
+
113
+ Note that for any frame, frame * frame.inverse() (or frame.inverse() *
114
+ frame) must yield the identity transformation:
115
+
116
+ ```
117
+ result = frame * frame.inverse()
118
+ => #<HomogeneousTransformation:0x000000024c0a20 @q=(1.0, Vector[0.0, 0.0, 0.0]), @t=Vector[-4.440892098500626e-16, -4.440892098500626e-16, -8.881784197001252e-16]>
119
+
120
+ result.getMatrix()
121
+ => Matrix[[1.0, 0.0, 0.0, -4.440892098500626e-16], [0.0, 1.0, 0.0, -4.440892098500626e-16], [0.0, 0.0, 1.0, -8.881784197001252e-16], [0, 0, 0, 1]]
122
+ ```
123
+
124
+ ## Advanced Features
125
+
126
+ The initialize, setQuaternion, and setTranslation methods all accept
127
+ two additional parameters: a HomTran object called rel_to, and a
128
+ boolean value called local. The value of local is only used if rel_to
129
+ is not nil.
130
+
131
+ By default, when initializing a HomTran object, or setting its
132
+ orientation or position, this class assumes that the transformation is
133
+ being specified relative to the "global" or "inertial" reference
134
+ frame, or some other common parent frame. However, if a value for
135
+ rel_to is passed to any of the methods mentioned above, the
136
+ orientation and rotation are specified relative to the rel_to frame.
137
+
138
+ If local is true (its default value), then the location and
139
+ orientation should be specified in the rel_to frame. If local is
140
+ false, they should be specified in the "global" frame (or rel_to's
141
+ parent frame).
142
+
143
+ Perhaps an example will clarify things. Consider the following frame:
144
+
145
+ ```
146
+ frame1 = HomTran.new(UnitQuaternion.fromAngleAxis(Math::PI/2, Vector[0, 0, 1]), Vector[1, 0, 0])
147
+ => #<HomogeneousTransformation:0x00000002451490 @q=(0.7071067811865476, Vector[0.0, 0.0, 0.7071067811865475]), @t=Vector[1, 0, 0]>
148
+ ```
149
+
150
+ The frame is rotated by PI/2 radians about the global z-axis, and
151
+ offset by 1 unit along the global x-axis. Next, say we want to create
152
+ a new frame with the same orientation, but offset by another 1 unit
153
+ along the global x-axis. We could create such a frame as follows:
154
+
155
+ ```
156
+ frame2 = HomTran.new(UnitQuaternion.new(), Vector[1, 0, 0], frame1, false)
157
+ => #<HomogeneousTransformation:0x000000025d62c0 @q=(0.7071067811865476, Vector[0.0, 0.0, 0.7071067811865475]), @t=Vector[2, 0, 0]>
158
+
159
+ frame2.getTranslation()
160
+ => Vector[2, 0, 0]
161
+ ```
162
+
163
+ Notice that we specified [1, 0, 0] for frame2's translation, but,
164
+ since we passed in frame1 for the rel_to argument, the resulting
165
+ translation is [1, 0, 0] plus frame1's translation. Also, notice that
166
+ local was false, so we translated frame2 along the global x-axis.
167
+
168
+ Since frame1 is rotated by PI/2 about the global z-axis, frame2's
169
+ x-axis is aligned with the global y-axis. So, if we create a new
170
+ frame similar to frame2, but with local = true, we will translate it
171
+ along frame1's x-axis, which aligns with the global y-axis. So, the
172
+ resulting translation should be [1, 1, 0]. Let's try it:
173
+
174
+ ```
175
+ frame3 = HomTran.new(UnitQuaternion.new(), Vector[1, 0, 0], frame1, true)
176
+ => #<HomogeneousTransformation:0x000000025e7228 @q=(0.7071067811865476, Vector[0.0, 0.0, 0.7071067811865475]), @t=Vector[1.0000000000000002, 1.0, 0.0]>
177
+
178
+ frame3.getTranslation()
179
+ => Vector[1.0000000000000002, 1.0, 0.0]
180
+ ```
181
+
182
+ Exactly what we expected!
183
+
184
+ A note of caution: when you create a HomTran using the rel_to
185
+ parameter, this class calculates the resulting offset and orientation
186
+ relative to the "global" frame and returns the corresponding HomTran.
187
+ So, any subsequent changes to the rel_to frame will not affect the
188
+ child frame, unless you update it manually using the setQuaternion or
189
+ setTranslation methods with the rel_to parameter.
190
+
191
+ ## Contributing
192
+
193
+ 1. Fork it
194
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
195
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
196
+ 4. Push to the branch (`git push origin my-new-feature`)
197
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :test do
4
+ ruby 'tests/tests_HomogeneousTransformation.rb'
5
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'homogeneous_transformation/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "homogeneous_transformation"
8
+ spec.version = HomogeneousTransformation::VERSION
9
+ spec.authors = ["Cory Crean"]
10
+ spec.email = ["cory.crean@gmail.com"]
11
+ spec.description = %q{Provides a HomogeneousTransformation class to convert back and forth between the representations of vectors in different reference frames}
12
+ spec.summary = %q{Provides a HomogeneousTransformation class}
13
+ spec.homepage = "https://github.com/ccrean/HomTran_ruby"
14
+ spec.license = "BSD"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = ">=1.9.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "simplecov", "~> 0.11.2"
26
+ spec.add_development_dependency "simplecov-html", "~> 0.10.0"
27
+ spec.add_runtime_dependency "unit_quaternion", ">= 0.0.5"
28
+ end
@@ -0,0 +1,136 @@
1
+ # Author:: Cory Crean (mailto:cory.crean@gmail.com)
2
+ # Copyright:: Copyright (c) 2016 Cory Crean
3
+ # License:: BSD
4
+ #
5
+ # A homogeneous transformation class, for converting between the
6
+ # representations of vectors in different reference frames.
7
+
8
+ require_relative 'homogeneous_transformation/version'
9
+ require 'unit_quaternion'
10
+ require 'matrix'
11
+
12
+ class HomogeneousTransformation
13
+ # Creates a new HomogeneousTransformation from a quaternion and a vector.
14
+ #
15
+ # Params:
16
+ # +q+:: A UnitQuaternion object representing this frame's orientation relative to the rel_to frame.
17
+ # +translation+:: A 3-vector representing this frame's translation relative to the rel_to frame.
18
+ # +rel_to+:: A HomogeneousTransformation object relative to which the current frame's orientation and translation are specified. If rel_to is nil, the orientation and translation will be specified relative to the global reference frame.
19
+ # +local+:: A boolean value. If local is false, the relative position and orientation will be measured in the global reference frame. If local is true, the relative position and orientation will be meausured in rel_to's reference frame.
20
+ def initialize(q = UnitQuaternion.new(1, 0, 0, 0),
21
+ translation = Vector[0, 0, 0],
22
+ rel_to = nil, local = true)
23
+ setQuaternion(q, rel_to, local)
24
+ setTranslation(translation, rel_to, local)
25
+ end
26
+
27
+ # Creates a homogeneous transformation given a suitable 4x4 matrix.
28
+ #
29
+ # Params:
30
+ # +m+:: A 4x4 matrix. The upper left 3x3 submatrix must be orthonormal, and the final row must be [0, 0, 0, 1].
31
+ def self.fromMatrix(m)
32
+ h = HomogeneousTransformation.new()
33
+ h.setMatrix(m)
34
+ return h
35
+ end
36
+
37
+ # Returns the quaternion that represents the orientation of the homogeneous transformation.
38
+ def getQuaternion()
39
+ return @q
40
+ end
41
+
42
+ # Returns the 3-vector that specifies the translation of the homogeneous transformation.
43
+ def getTranslation()
44
+ return @t
45
+ end
46
+
47
+ # Sets the quaternion for the homogeneous transformation.
48
+ #
49
+ # Params:
50
+ # +q+:: A UnitQuaternion object representing the orientation of this frame.
51
+ # +rel_to+:: A HomogeneousTransformation object relative to which the orientation is specified. If rel_to is nil, the orientation will be specified relative to the global reference frame.
52
+ # +local+:: A boolean value. If local is false, the quaternion q specifies the orientation relative to rel_to as measured in the global coordinate frame. If local is true, then q specifies the orientation relative to rel_to as measured in rel_to's coordinate frame.
53
+ def setQuaternion(q, rel_to = nil, local = true)
54
+ if rel_to
55
+ if local
56
+ @q = rel_to.getQuaternion() * q
57
+ else
58
+ @q = q * rel_to.getQuaternion()
59
+ end
60
+ else
61
+ @q = q
62
+ end
63
+ end
64
+
65
+ # Sets the translation for the homogeneous transformation.
66
+ #
67
+ # Params:
68
+ # +t+:: A vector describing the frame's translation.
69
+ # +rel_to+:: A HomogeneousTransformation object relative to which the translation is specified. If rel_to is nil, the translation will be specified relative to the global reference frame.
70
+ # +local+:: A boolean value. If local is false, then t specifies the translation relative to rel_to as measured in the global coordinate frame. If local is true, then t specifies the translation relative to rel_to as measured in rel_to's coordinate frame.
71
+ def setTranslation(t, rel_to = nil, local = true)
72
+ if rel_to
73
+ if local
74
+ @t = rel_to.getTranslation() +
75
+ rel_to.getQuaternion().transform(t)
76
+ else
77
+ @t = rel_to.getTranslation() + t
78
+ end
79
+ else
80
+ @t = t
81
+ end
82
+ end
83
+
84
+ # Sets the value of the homogeneous transformation given a suitable 4x4 matrix.
85
+ #
86
+ # Params:
87
+ # +m+:: A 4x4 matrix. The upper left 3x3 submatrix must be orthonormal, and the final row must be [0, 0, 0, 1].
88
+ def setMatrix(m)
89
+ if m.row_size() != 4 or m.column_size() != 4
90
+ raise(ArgumentError, "Matrix must be 4x4")
91
+ end
92
+ if (m.row(3) - Vector[0,0,0,1]).norm() > 1e-15
93
+ raise(ArgumentError, "Final row must be [0, 0, 0, 1]")
94
+ end
95
+ @q.setRotationMatrix(m.minor(0..2, 0..2))
96
+ @t = m.column(3)[0..2]
97
+ end
98
+
99
+ # Takes a vector v representing a point in the local frame and returns the
100
+ # vector to that same point relative to the global frame.
101
+ #
102
+ # Params:
103
+ # +v+:: A vector in the local reference frame.
104
+ #
105
+ # Returns:
106
+ # The representation of v in the global reference frame.
107
+ def transform(v)
108
+ return @t + @q.transform(v)
109
+ end
110
+
111
+ # Returns the inverse of the homogeneous transformation.
112
+ def inverse
113
+ q_inv = @q.inverse()
114
+ t_inv = q_inv.transform(-1 * @t)
115
+ return HomogeneousTransformation.new(q_inv, t_inv)
116
+ end
117
+
118
+ # Returns the 4x4 matrix representation of the homogeneous transformation.
119
+ def getMatrix
120
+ rot_mat = @q.getRotationMatrix()
121
+ m = Matrix.columns([*rot_mat.transpose(), @t])
122
+ m = Matrix.rows([*m, [0, 0, 0, 1]])
123
+ return m
124
+ end
125
+
126
+ # Compose two homogeneous transformations.
127
+ def *(other)
128
+ q_result = self.getQuaternion() * other.getQuaternion()
129
+ t_result = self.getQuaternion().transform(other.getTranslation()) +
130
+ self.getTranslation()
131
+ result = HomogeneousTransformation.new(q_result, t_result)
132
+ return result
133
+ end
134
+ end
135
+
136
+ HomTran = HomogeneousTransformation
@@ -0,0 +1,3 @@
1
+ class HomogeneousTransformation
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,234 @@
1
+ # Name: tests_HomogeneousTransformation.rb
2
+ # Description: Test cases for the HomogeneousTransformation class
3
+ # Author: Cory Crean
4
+ # E-mail: cory.crean@gmail.com
5
+ # Copyright (c) 2016, Cory Crean
6
+
7
+ require 'simplecov'
8
+ SimpleCov.start
9
+ require 'test/unit'
10
+ require 'matrix'
11
+ require 'unit_quaternion'
12
+ require_relative '../lib/homogeneous_transformation'
13
+
14
+ def isIdentityMatrix(m, tol)
15
+ for i, j in [0,1,2].product([0,1,2])
16
+ if i == j
17
+ if (m[i,j] - 1).abs > tol
18
+ return false
19
+ end
20
+ else
21
+ if m[i,j].abs > tol
22
+ return false
23
+ end
24
+ end
25
+ end
26
+ return true
27
+ end
28
+
29
+ def areEqualMatrices(m1, m2, tol)
30
+ m1.zip(m2).each() do |v1, v2|
31
+ if (v1 - v2).abs() > tol
32
+ puts("error = ", (v1 - v2).abs())
33
+ return false
34
+ end
35
+ end
36
+ return true
37
+ end
38
+
39
+ def buildMatrix(q, t)
40
+ m = q.getRotationMatrix()
41
+ m = Matrix[*m.transpose(), t].transpose()
42
+ m = Matrix[*m, [0, 0, 0, 1]]
43
+ end
44
+
45
+ class TestHomogeneousTransformation < Test::Unit::TestCase
46
+
47
+ def setup
48
+ @quats = [ ::UnitQuaternion.new(1,2,3,4),
49
+ ::UnitQuaternion.new(0.1, 0.01, 2.3, 4),
50
+ ::UnitQuaternion.new(1234.4134, 689.6124, 134.124, 0.5),
51
+ ::UnitQuaternion.new(1,1,1,1),
52
+ UnitQuaternion.new(1, 0, 0, 0),
53
+ ]
54
+ @angles = [ 2*Math::PI, Math::PI, Math::PI/2, Math::PI/4,
55
+ 0.5, 0.25, 0.1234, ]
56
+ @axes = [ Vector[ 1, 1, 1 ], Vector[ 1, 0, 0 ], Vector[ 0, 1, 0 ],
57
+ Vector[ 0, 0, 1 ], Vector[ 1, 2, 3 ], ]
58
+ for angle, axis in @angles.product(@axes)
59
+ @quats << UnitQuaternion.fromAngleAxis(angle, axis)
60
+ end
61
+
62
+ positions = (0..1).step(0.5).to_a
63
+ @translations = []
64
+ positions.product(positions, positions).each() do |a, b, c|
65
+ @translations << Vector[a, b, c]
66
+ end
67
+ end
68
+
69
+ def test_initialize
70
+ ht = HomTran.new()
71
+ assert_equal(ht.getQuaternion(), UnitQuaternion.new(1, 0, 0, 0))
72
+ assert_equal(ht.getTranslation(), Vector[0, 0, 0])
73
+
74
+ q = UnitQuaternion.new(1,2,3,4)
75
+ t = Vector[5,6,7]
76
+ ht = HomTran.new(q, t)
77
+ assert_equal(ht.getQuaternion(), q)
78
+ assert_equal(ht.getTranslation(), t)
79
+
80
+ @quats.product(@translations).each() do |q2, t2|
81
+ fr = HomTran.new(q2, t2, ht)
82
+ assert_in_delta( (fr.getQuaternion() - q * q2).norm(), 0, 1e-15)
83
+ assert_in_delta( (fr.getTranslation() - (t + q.transform(t2))).norm(),
84
+ 0, 1e-15)
85
+
86
+ fr = HomTran.new(q2, t2, ht, false)
87
+ assert_in_delta( (fr.getQuaternion() - q2 * q).norm(), 0, 1e-15)
88
+ assert_in_delta( (fr.getTranslation() - (t + t2)).norm(), 0, 1e-15)
89
+ end
90
+ end
91
+
92
+ def test_setQuaternion
93
+ for q in @quats
94
+ fr1 = HomTran.new()
95
+ fr1.setQuaternion(q)
96
+ assert_equal(fr1.getQuaternion(), q)
97
+ end
98
+ for q1, q2 in @quats.product(@quats)
99
+ fr1 = HomTran.new(q1)
100
+ fr2 = HomTran.new()
101
+ fr2.setQuaternion(q2, fr1)
102
+ assert_in_delta( (fr2.getQuaternion() - q1 * q2).norm(), 0, 1e-15 )
103
+
104
+ fr2.setQuaternion(q2, fr1, false)
105
+ assert_in_delta( (fr2.getQuaternion() - q2 * q1).norm(), 0, 1e-15 )
106
+ end
107
+ end
108
+
109
+ def test_setTranslation
110
+ @quats.product(@translations).each() do |q, t|
111
+ fr1 = HomTran.new(q)
112
+ fr1.setTranslation(t)
113
+ assert_in_delta( (fr1.getTranslation() - t).norm(), 0, 1e-15 )
114
+
115
+ fr2 = HomTran.new()
116
+ fr2.setTranslation(t, fr1)
117
+ assert_in_delta( (fr1.getTranslation() +
118
+ fr1.getQuaternion().transform(t) -
119
+ fr2.getTranslation()).norm(), 0, 1e-15 )
120
+
121
+ fr2.setTranslation(t, fr1, false)
122
+ assert_in_delta( (fr2.getTranslation - 2 * t).norm(), 0, 1e-15)
123
+ end
124
+ end
125
+
126
+ def test_transform
127
+ t = Vector[1,2,3]
128
+ fr = HomTran.new(UnitQuaternion.fromAngleAxis(Math::PI/2,
129
+ Vector[0, 0, 1]), t)
130
+ assert_in_delta( (fr.transform(Vector[2, 0, 0]) -
131
+ (t + Vector[0, 2, 0])).norm(), 0, 1e-15)
132
+ assert_in_delta( (fr.transform(Vector[0, 2, 0]) -
133
+ (t + Vector[-2, 0, 0])).norm(), 0, 1e-15)
134
+
135
+ fr = HomTran.new(UnitQuaternion.fromAngleAxis(Math::PI/2,
136
+ Vector[0, 1, 0]), t)
137
+ assert_in_delta( (fr.transform(Vector[2, 0, 0]) -
138
+ (t + Vector[0, 0, -2])).norm(), 0, 1e-15)
139
+ assert_in_delta( (fr.transform(Vector[0, 0, 2]) -
140
+ (t + Vector[2, 0, 0])).norm(), 0, 1e-15)
141
+
142
+ fr = HomTran.new(UnitQuaternion.fromAngleAxis(Math::PI/2,
143
+ Vector[1, 0, 0]),
144
+ t)
145
+ assert_in_delta( (fr.transform(Vector[2, 0, 0]) -
146
+ (t + Vector[2, 0, 0])).norm(), 0, 1e-15)
147
+ assert_in_delta( (fr.transform(Vector[0, 2, 0]) -
148
+ (t + Vector[0, 0, 2])).norm(), 0, 1e-15)
149
+ assert_in_delta( (fr.transform(Vector[0, 0, 2]) -
150
+ (t + Vector[0, -2, 0])).norm(), 0, 1e-15)
151
+ end
152
+
153
+ def test_inverse
154
+ vectors = [ Vector[1,2,3], Vector[0,0,1], Vector[0.1,0.1,0.1] ]
155
+ @quats.product(@translations).each() do |q, t|
156
+ fr = HomTran.new(q, t)
157
+ vectors.each() do |v|
158
+ transformed = fr.inverse().transform(fr.transform(v))
159
+ assert_in_delta( (v - transformed).norm(), 0, 1e-14)
160
+ end
161
+ end
162
+ end
163
+
164
+ def test_matrix
165
+ @quats.product(@translations).each() do |q, t|
166
+ fr = HomTran.new(q, t)
167
+ fr_inv = fr.inverse()
168
+ assert(isIdentityMatrix( fr.getMatrix() * fr_inv.getMatrix(), 1e-15))
169
+ end
170
+
171
+ @quats.product(@translations).each() do |q, t|
172
+ fr = HomTran.new()
173
+ fr.setMatrix(buildMatrix(q, t))
174
+ assert(areEqualMatrices(fr.getQuaternion().getRotationMatrix(),
175
+ q.getRotationMatrix(), 1e-15))
176
+ assert(areEqualMatrices(fr.getTranslation(), t, 1e-15))
177
+ end
178
+
179
+ @quats.product(@translations).each() do |q, t|
180
+ fr = HomTran.fromMatrix(buildMatrix(q, t))
181
+ assert(areEqualMatrices(fr.getQuaternion().getRotationMatrix(),
182
+ q.getRotationMatrix(), 1e-15))
183
+ assert(areEqualMatrices(fr.getTranslation(), t, 1e-15))
184
+ end
185
+
186
+ q = UnitQuaternion.new(1,2,3,4)
187
+ t = Vector[1,2,3]
188
+ m = buildMatrix(q, t)
189
+ m2 = Matrix[m.row(0), m.row(1), m.row(2), [0.01, 0, 0, 1]]
190
+ ht = HomTran.new()
191
+ assert_raise(ArgumentError) do
192
+ ht.setMatrix(m2)
193
+ end
194
+ assert_raise(ArgumentError) do
195
+ HomTran.fromMatrix(m2)
196
+ end
197
+
198
+ m2 = Matrix[m.row(0), m.row(1), m.row(2), [0, 0, 0, 1.0001]]
199
+ assert_raise(ArgumentError) do
200
+ ht.setMatrix(m2)
201
+ end
202
+ assert_raise(ArgumentError) do
203
+ HomTran.fromMatrix(m2)
204
+ end
205
+
206
+ m2 = Matrix[ [1, 0.001, 0, 1], [0, 1, 0, 2], [0, 0, 1, 3],
207
+ [0, 0, 0, 1] ]
208
+ assert_raise(ArgumentError) do
209
+ ht.setMatrix(m2)
210
+ end
211
+ assert_raise(ArgumentError) do
212
+ HomTran.fromMatrix(m2)
213
+ end
214
+
215
+ m2 = Matrix[ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0] ]
216
+ assert_raise(ArgumentError) do
217
+ ht.setMatrix(m2)
218
+ end
219
+ assert_raise(ArgumentError) do
220
+ HomTran.fromMatrix(m2)
221
+ end
222
+ end
223
+
224
+ def test_multiply
225
+ parts = @quats.product(@translations)
226
+ for i in 0...(parts.length() - 1)
227
+ fr1 = HomTran.new(*parts[i])
228
+ fr2 = HomTran.new(*parts[i+1])
229
+ result = fr1 * fr2
230
+ assert(areEqualMatrices(result.getMatrix(),
231
+ fr1.getMatrix() * fr2.getMatrix(), 1e-15))
232
+ end
233
+ end
234
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: homogeneous_transformation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Cory Crean
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-08-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: simplecov
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.11.2
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.11.2
62
+ - !ruby/object:Gem::Dependency
63
+ name: simplecov-html
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.10.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.10.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: unit_quaternion
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 0.0.5
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 0.0.5
94
+ description: Provides a HomogeneousTransformation class to convert back and forth
95
+ between the representations of vectors in different reference frames
96
+ email:
97
+ - cory.crean@gmail.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - LICENSE.txt
105
+ - README.md
106
+ - Rakefile
107
+ - homogeneous_transformation.gemspec
108
+ - lib/homogeneous_transformation.rb
109
+ - lib/homogeneous_transformation/version.rb
110
+ - tests/tests_HomogeneousTransformation.rb
111
+ homepage: https://github.com/ccrean/HomTran_ruby
112
+ licenses:
113
+ - BSD
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: 1.9.3
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 1.8.23
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: Provides a HomogeneousTransformation class
136
+ test_files: []