Commits

Ross Light  committed 80ed06c

Add quat32 package (still in development)

  • Participants
  • Parent commits 031803d

Comments (0)

Files changed (3)

 
 Documentation is available at http://godoc.org/bitbucket.org/zombiezen/math3.
 
+
+API Stability
+===============
+
+As much as possible, these packages are under the same API change restrictions
+as the core Go libraries.  quat32 and quat64 are experimental packages, so their
+API may still change.  This is expected to stabilize soon.
+
 License
 =========
 

File quat32/quat.go

+// Package quat32 operates on float32 quaternions.
+package quat32
+
+import (
+	"bitbucket.org/zombiezen/math3/vec32"
+	"math"
+)
+
+// A Quaternion holds a four-dimensional quaternion.  The values are stored in {X, Y, Z, W} order.
+type Quaternion [4]float32
+
+// Length returns the norm of q.
+func (q Quaternion) Length() float32 {
+	return float32(math.Sqrt(float64(q.LengthSqr())))
+}
+
+// LengthSqr returns the norm squared of q.  This is cheaper to compute than Length.
+func (q Quaternion) LengthSqr() float32 {
+	return q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]
+}
+
+// Negate returns a new quaternion that applies an opposite rotation.
+func (q Quaternion) Negate() Quaternion {
+	return Quaternion{q[0], q[1], q[2], -q[3]}
+}
+
+// Conjugate returns q's conjugate.
+func (q Quaternion) Conjugate() Quaternion {
+	return Quaternion{-q[0], -q[1], -q[2], q[3]}
+}
+
+// Transform computes the rotation q for point v.
+// Mathematically, this is the product of q, v, and q's conjugate.
+func (q Quaternion) Transform(v vec32.Vector) vec32.Vector {
+	qq := Mul(Mul(q, Quaternion{v[0], v[1], v[2]}), q.Conjugate())
+	return vec32.Vector{qq[0], qq[1], qq[2]}
+}
+
+// Mul calculates the product of q1 and q2.  The result corresponds to the rotation q2 followed by the rotation q1.
+func Mul(q1, q2 Quaternion) Quaternion {
+	v1, v2 := vec32.Vector(q1).Vec3(), vec32.Vector(q2).Vec3()
+	v3 := vec32.Add(vec32.Add(v2.Scale(q1[3]), v1.Scale(q2[3])), vec32.Cross(v1, v2))
+	return Quaternion{v3[0], v3[1], v3[2], q1[3]*q2[3] - vec32.Dot(v1, v2)}
+}
+
+// AxisAngle builds a new quaternion from a vector (which will be normalized)
+// and an angle in radians.
+func AxisAngle(axis vec32.Vector, angle float32) Quaternion {
+	axis = axis.Normalize()
+	sin, cos := float32(math.Sin(float64(angle/2))), float32(math.Cos(float64(angle/2)))
+	return Quaternion{
+		-axis[0] * sin,
+		-axis[1] * sin,
+		-axis[2] * sin,
+		cos,
+	}
+}

File quat32/quat_test.go

+package quat32
+
+import (
+	"bitbucket.org/zombiezen/math3/vec32"
+	"math"
+	"testing"
+)
+
+const checkTol = 0.01
+
+func TestAxisAngle(t *testing.T) {
+	tests := []struct {
+		Axis  vec32.Vector
+		Angle float32
+		Q     Quaternion
+	}{
+		{vec32.Vector{0, 0, 1}, math.Pi / 2, Quaternion{0, 0, -float32(1 / math.Sqrt(2)), float32(1 / math.Sqrt(2))}},
+	}
+	for _, test := range tests {
+		q := AxisAngle(test.Axis, test.Angle)
+		if !checkQuaternion(q, test.Q, checkTol) {
+			t.Errorf("AxisAngle(%v, %v) = %v; want %v", test.Axis, test.Angle, q, test.Q)
+		}
+	}
+}
+
+func TestTransform(t *testing.T) {
+	tests := []struct {
+		Q   Quaternion
+		V   vec32.Vector
+		Out vec32.Vector
+	}{
+		{AxisAngle(vec32.Vector{0, 0, 1}, -math.Pi/2), vec32.Vector{2, 0, 0}, vec32.Vector{0, 2, 0}},
+	}
+	for _, test := range tests {
+		out := test.Q.Transform(test.V)
+		if !checkVector(out, test.Out, checkTol) {
+			t.Errorf("%v.Transform(%v) = %v; want %v", test.Q, test.V, out, test.Out)
+		}
+	}
+}
+
+// checkVector returns whether v1 ~ v2, given a tolerance.
+func checkVector(v1, v2 vec32.Vector, tol float32) bool {
+	for i := range v1 {
+		if v2[i] > v1[i]+tol || v2[i] < v1[i]-tol {
+			return false
+		}
+	}
+	return true
+}
+
+// checkQuaternion returns whether q1 ~ q2, given a tolerance.
+func checkQuaternion(q1, q2 Quaternion, tol float32) bool {
+	for i := range q1 {
+		if q2[i] > q1[i]+tol || q2[i] < q1[i]-tol {
+			return false
+		}
+	}
+	return true
+}