Rotation Quaternion class

Hi,

Quaternions is a great way to rotate points around a vector and avoid gimble lock.
It can be hard to understand, but is actually quite simple…

With help from youtube and other places I have made a simple RotationQuaternion class.

In this snippet it rotates a point 36 times around a vector and make a circle…

Enjoy!

image

Installation:
open ruby console --> use this snippet pasted into the Ruby Console +enter to actually open the folder itself:" UI.openURL(“file:///#{Sketchup.find_support_file(‘Plugins’)}”)

Copy the files from zip into folder. It was made in SU2016.

Restart SU and press the new button: image

rs_quaternionSU.zip (9.1 KB)

rs_quaternionSU.zip (9.1 KB)

if the installaton dont work the code is here: (2 files)
rs_quaternion_main.rb:

module RS_2019; end
module RS_2019::RS_Quaternion

load 'rs_quaternionSU\rs_quaternion_class.rb'
SKETCHUP_CONSOLE.show
SKETCHUP_CONSOLE.clear

puts "hello quaternion"

@model = Sketchup.active_model 
@ents = @model.active_entities
@defs = @model.definitions

rot_a_deg = 0
rot_axis = {"x" => rand(20), "y" => rand(20), "z" => rand(20)}	
point1 = [20,0,0]

q = RsRotationQuaternion.new(rot_a_deg, rot_axis)

@model.start_operation "rotate_point_and_draw"
  while rot_a_deg <= 360	
    rotated_point = q.point_to_rotate(point1)

    @ents.add_cpoint rotated_point

    rot_a_deg = rot_a_deg + 10
    q.set(rot_a_deg,rot_axis)
  end
@ents.add_line [0,0,0], [rot_axis['x'], rot_axis['y'], rot_axis['z']]
@model.commit_operation

end #module RS_2019::RS_Quaternion

next file: rs_quaternion_class.rb:

module RS_2019; end
module RS_2019::RS_Quaternion

class RsRotationQuaternion

def initialize(rot_a_deg, rot_axis)

  a = rot_a_deg * Math::PI / 180
  v_norm = Math.sqrt(rot_axis['x']**2 + rot_axis['y']**2 + rot_axis['z']**2)

   vx = rot_axis['x'] / v_norm # x rotation axis/unit vector
   vy = rot_axis['y'] / v_norm # y rotation axis/unit vector
   vz = rot_axis['z'] / v_norm # z rotation axis/unit vector
	
  # create rotation quaternion
  qa = Math.cos(a/2) # angle to rotate
  qb = vx * Math.sin(a/2)
  qc = vy * Math.sin(a/2)
  qd = vz * Math.sin(a/2)
  q_check = qa**2 + qb**2 + qc**2 + qd**2 # Result always 1
  # puts "q_check : #{q_check}" # Delete
	
  @q = {"a" => qa, "b" => qb, "c" => qc, "d" => qd}		

  # create conjugate rotation quaternion (inverse)
  qa_inv = qa
  qb_inv = -qb
  qc_inv = -qc
  qd_inv = -qd
  q_inv_check = qa_inv**2 + qb_inv**2 + qc_inv**2 + qd_inv**2 # Result always 1
  # puts "q_inv_check : #{q_inv_check}" # Delete 

  @q_inv = {"a" => qa_inv, "b" => qb_inv, "c" => qc_inv, "d" => qd_inv}

end # initialize

def set(rot_a_deg, rot_axis)
  initialize(rot_a_deg, rot_axis)
end

def point_to_rotate(point)
	
  p = {"a" => 0, "b" => point[0], "c" => point[1], "d" => point[2]}

# p' = q*p*q_inv, where p' is the rotated point, q the rotation quaternion, p the point to rotate and q_inv the conjugate rotation quaternion
   # To calculate a rotation of a point with a quaternion is a 2 step operation
   # First step is to calculate p_half_rot = q * p.
   # Second step is to calculate p' = p_half_rot * q_inv.

  # Step 1:		
  p_half_rot = multiply_quaternions(@q,p)

  # Step 2:
  p_rot = multiply_quaternions(p_half_rot, @q_inv)	

  return [p_rot['b'], p_rot['c'], p_rot['d']]

end # rotationQuaternion

def multiply_quaternions(q1, q2)

  q1r = q1['a']
  q1i = q1['b']
  q1j = q1['c']
  q1k = q1['d']

  q2r = q2['a']
  q2i = q2['b']
  q2j = q2['c']
  q2k = q2['d']


  # https://math.stackexchange.com/questions/296349/quaternions-why-does-ijk-1-and-ij-k-and-ji-k	
  # https://www.youtube.com/watch?v=LjSD103Rw4E
  # https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
  # r => real number 
  # x-axis: i^2 = -1, ij = k, ji = -k
  # y-axis: j^2 = -1, jk = i, kj = -i
  # z-axis: k^2 = -1, ki = j, ik = -j
	
	#| q1r           | q1i            | q1j            | q1k           ||
	#=====================================================================
	#| (q1r*q2r) (r) | (q1i*q2r) (i)  | (q1j*q2r) (j)  | (q1k*q2r) (k) || q2r
	#---------------------------------------------------------------------
	#| (q1r*q2i) (i) |-(q1i*q2i) (-r) |-(q1j*q2i) (-k) | (q1k*q2i) (j) || q2i
	#---------------------------------------------------------------------
	#| (q1r*q2j) (j) | (q1i*q2j) (k)  |-(q1j*q2j) (-r) |-(q1k*q2j) (-i) || q2j
	#---------------------------------------------------------------------
	#| (q1r*q2k) (k) |-(q1i*q2k) (-j) | (q1j*q2k) (i)  |-(q1k*q2k) (-r)|| q2k
	#---------------------------------------------------------------------

  q12r = (q1r*q2r)-(q1i*q2i)-(q1j*q2j)-(q1k*q2k)     
  q12i = (q1i*q2r)+(q1r*q2i)-(q1k*q2j)+(q1j*q2k)
  q12j = (q1j*q2r)+(q1k*q2i)+(q1r*q2j)-(q1i*q2k)
  q12k = (q1k*q2r)-(q1j*q2i)+(q1i*q2j)+(q1r*q2k)

  return {"a" => q12r, "b" => q12i, "c" => q12j, "d" => q12k}

end # multiply_quaternions(...)

end # class RsRotationQuaternion
end #module RS_2019::RS_Quaternion

Please rename the zip file to rbz so it can be auto installed from the Extension Manager (or the old Extenions panel) via the “Install Extension” button.

(We no longer suggest users open the “Plugins” folder and manually copy files.)

I tried to change zip to rbz… but when uploaded it changed it to zip again…

… must be some “nanny” quirk in Discourse.