Class: Quaternion

Inherits:
Object show all
Defined in:
lib/quaternion.rb

Overview

Quaternion instances represents Quaternions, complex numbers with one real part and three imaginary parts. While the math is a bit obscure, they can be used to manipulate 3D rotations without “gimbal lock”, the problem of coincident viewing angle alignment problems at the boundaries, for example, where the difference between the x and y coordinates of the viewing vector are zero (ie. looking straight up or straight down.)

Interestingly, Quaternions were first conceptualized by Hamilton in 1843, well before the widespread use of vector notation. While mostly put on the shelf, they still solve certain problems elegantly, and in some cases more quickly than matrix-based 3D calulations.

The quaternion’s real part is accessed through the real instance variable, the three complex parts as the quaternion’s “axis” vector, a Point3.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(axis = origin, real = 0) ⇒ Quaternion

Creates and returns a Quaternion instance. Sets the coordinates values using the set method.



28
29
30
# File 'lib/quaternion.rb', line 28

def initialize(axis=origin,real=0)
  set axis, real
end

Instance Attribute Details

#axisObject

complex axis vector part reader and writer



22
23
24
# File 'lib/quaternion.rb', line 22

def axis
  @axis
end

#realObject

real part reader and writer



24
25
26
# File 'lib/quaternion.rb', line 24

def real
  @real
end

Class Method Details

.from_axis_angle(axis, angle) ⇒ Object

Returns a new Quaternion-based encoding of a rotation.



79
80
81
82
83
# File 'lib/quaternion.rb', line 79

def Quaternion.from_axis_angle(axis,angle)
  half_angle = angle/2.0
  Quaternion.new Point3.new(axis).scale(Math.sin(half_angle)),
                 Math.cos(half_angle)
end

Instance Method Details

#==(quaternion) ⇒ Object

Returns true if the parts of the instance are equal to the parts of the given quaternion.



56
57
58
# File 'lib/quaternion.rb', line 56

def ==(quaternion)
  (@axis == quaternion.axis)&&(@real == quaternion.real)
end

#absObject

Returns the absolute value of the instance.



205
206
207
# File 'lib/quaternion.rb', line 205

def abs
  Math.sqrt(norm)
end

#add(axis = origin, real = 0) ⇒ Object Also known as: +

Returns a new Quaternion instance whose parts are the original instance’s with the given amounts added:

  • axis is a Quaternion, its parts are added.

  • axis responds like a Point3, the arguments are added.

  • otherwise a TypeError is raised.



99
100
101
# File 'lib/quaternion.rb', line 99

def add(axis=origin,real=0)
  quaternion.add!(axis,real)
end

#add!(axis = origin, real = 0) ⇒ Object

Returns the modified instance with the arguments added.



106
107
108
109
110
111
112
113
114
115
# File 'lib/quaternion.rb', line 106

def add!(axis=origin,real=0)
  if axis.kind_of? Quaternion
    add!(axis.axis,axis.real)
  elsif axis.point3_like?
    set(@axis+axis,@real+real)
  else
    raise_no_conversion axis
  end
  self
end

#approximately_equals?(quaternion, epsilon = Numeric.epsilon) ⇒ Boolean Also known as: =~

Returns true if the parts of the instance are approximately equal to the parts of the given quaternion, each part less than a distance epsilon from the target.

Returns:

  • (Boolean)


63
64
65
66
# File 'lib/quaternion.rb', line 63

def approximately_equals?(quaternion,epsilon=Numeric.epsilon)
  (@axis.approximately_equals?(quaternion.axis,epsilon)&&
   @real.approximately_equals?(quaternion.real,epsilon))
end

#conjugateObject

Returns a new Quaternion instance that is the conjugate of the original instance.



190
191
192
# File 'lib/quaternion.rb', line 190

def conjugate
  quaternion.conjugate!
end

#conjugate!Object

Returns the modified instance replaced with its conjugate.



195
196
197
# File 'lib/quaternion.rb', line 195

def conjugate!
  set(@axis.mirror,@real)
end

#divide(axis = origin, real = 1) ⇒ Object

Returns a new Quaternion instance whose parts are the original instance’s divided by the given amounts:

  • axis is a Quaternion, the instance is divided by the axis’ parts.

  • axis responds like a Point3, the instance is divided by the arguments.

  • otherwise a TypeError is raised.



226
227
228
# File 'lib/quaternion.rb', line 226

def divide(axis=origin,real=1)
  quaternion.divide!(axis,origin)
end

#divide!(axis = origin, real = 1) ⇒ Object

Returns the modified instance divided by the arguments.



231
232
233
234
235
236
237
238
239
240
# File 'lib/quaternion.rb', line 231

def divide!(axis=origin,real=1)
  if axis.kind_of? Quaternion
    divide!(axis.axis,axis.real)
  elsif axis.point3_like?
    multiply!(quaternion(axis,real).inverse!)
  else
    raise_no_conversion axis
  end
  self
end

#inverseObject

Returns a new Quaternion instance that is the inverse of the original instance.



211
212
213
# File 'lib/quaternion.rb', line 211

def inverse
  quaternion.inverse!
end

#inverse!Object

Returns the modified instance replaced with its inverse.



216
217
218
219
# File 'lib/quaternion.rb', line 216

def inverse!
  conjugate!
  scale!(1.0/norm)
end

#multiply(axis = origin, real = 1) ⇒ Object Also known as: *

Returns a new Quaternion instance whose parts are the original instance’s multiplied by the given amounts:

  • axis is a Quaternion, the instance is multiplied by the axis’ parts.

  • axis responds like a Point3, the instance is multiplied by the arguments.

  • otherwise a TypeError is raised.



145
146
147
# File 'lib/quaternion.rb', line 145

def multiply(axis=origin,real=1)
  quaternion.multiply!(axis,real)
end

#multiply!(axis = origin, real = 1) ⇒ Object

Returns the modified instance multiplied by the arguments.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/quaternion.rb', line 152

def multiply!(axis=origin,real=1)
  if axis.kind_of? Quaternion
    multiply!(axis.axis,axis.real)
  elsif axis.point3_like?
    r = (@real*real)-@axis.dot(axis)
    p3 = axis.scale r
    qp3 = @axis.scale real
    cross = @axis.cross axis
    a = p3.add!(qp3).add!(cross)
    set(a,r)
  else
    raise_no_conversion axis
  end
  self
end

#normObject

Returns the norm of the instance.



200
201
202
# File 'lib/quaternion.rb', line 200

def norm
  (@real * @real) + @axis.modulus
end

#quaternion(axis = self.axis, real = self.real) ⇒ Object

Returns a copy of a Quaternion with the given parts:

  • axis is a Quaternion, its parts are copied.

  • axis responds like a Point3, the arguments are copied.

  • otherwise a TypeError is raised.



74
75
76
# File 'lib/quaternion.rb', line 74

def quaternion(axis=self.axis,real=self.real)
  Quaternion.new(axis,real)
end

#scale(scalar = 1) ⇒ Object

Returns a new Quaternion instance whose parts are the original instance’s multiplied by the scalar:

  • scalar is a Quaternion, the instance is multiplied by the scalar’s parts.

  • axis is a Numeric, the instance’s parts are multiplied by the scalar.

  • otherwise a TypeError is raised.



173
174
175
# File 'lib/quaternion.rb', line 173

def scale(scalar=1)
  quaternion.scale!(scalar)
end

#scale!(scalar = 1) ⇒ Object

Returns the modified instance multiplied by the scalar.



178
179
180
181
182
183
184
185
186
# File 'lib/quaternion.rb', line 178

def scale!(scalar=1)
  if (scalar.kind_of? Quaternion)
    multiply!(scalar)
  elsif (scalar.kind_of? Numeric)
    set(@axis.scale(scalar),@real*scalar)
  else
    raise_no_conversion scalar, "Numeric"
  end
end

#set(axis = origin, real = 0) ⇒ Object

Sets the axis and real values of the instance. When

  • axis is a Quaternion, the arguments are interpretted as the parts of a quaternion.

  • axis reponds like a Point3, the axis and real are used to set the quaternion’s values.

  • otherwise a TypeError is raised.

The modified instance is returned.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/quaternion.rb', line 42

def set(axis=origin,real=0)
  if axis.kind_of? Quaternion
    set axis.axis, axis.real
  elsif axis.point3_like?
    (@axis ||= point3).set(axis)
    @real = real
  else
    raise_no_conversion(self.class.name,axis)
  end
  self
end

#subtract(axis = origin, real = 0) ⇒ Object Also known as: -

Returns a new Quaternion instance whose parts are the original instance’s with the given amounts subtracted:

  • axis is a Quaternion, its parts are subtracted.

  • axis responds like a Point3, the arguments are subtracted.

  • otherwise a TypeError is raised.



122
123
124
# File 'lib/quaternion.rb', line 122

def subtract(axis=origin,real=0)
  quaternion.subtract!(axis,real)
end

#subtract!(axis = origin, real = 0) ⇒ Object

Returns the modified instance with the arguments subtracted.



129
130
131
132
133
134
135
136
137
138
# File 'lib/quaternion.rb', line 129

def subtract!(axis=origin,real=0)
  if axis.kind_of? Quaternion
    subtract!(axis.axis,axis.real)
  elsif axis.point3_like?
    set(@axis-axis,@real-real)
  else
    raise_no_conversion axis
  end
  self
end

#to_axis_angleObject

Returns a new Quaternion encoded into a rotation.



86
87
88
89
90
91
92
# File 'lib/quaternion.rb', line 86

def to_axis_angle
  half_angle = Math.acos(@real)
  sin_half_angle = Math.sin(half_angle)
  axis = (sin_half_angle.abs < 0.00000001)?
    Point3.new(1,0,0) : Point3.new(@axis).scale!(1.0/sin_half_angle)
  Quaternion.new(axis,2.0*half_angle)
end

#to_sObject

Returns a string representation of the instance.



33
34
35
# File 'lib/quaternion.rb', line 33

def to_s
  "Quaternion:  axis: x #{axis.x}  y #{axis.y} z #{axis.z}   real #{real}"
end