unit ugluQuaternion; { Package: OpenGLCore Prefix: glu - OpenGL Utils Beschreibung: diese Unit enthält Quaternion-Typen und Methoden um diese zu erstellen und zu manipulieren } {$mode objfpc}{$H+} interface uses Classes, SysUtils, ugluVector, ugluMatrix; type TgluQuaternion = type TgluVector4f; // w,x,y,z { Winkel : rad, außer glRotate-Komponenten Absolute Werte : Orientation Relative Werte/"Drehanweisungen" : Rotation To/FromVector : Position im R^4 Alle Funktionen nehmen an, dass die Quaternion nur zur Rotation verwendet wird (kein Scale), mathematisch also normalisiert ist. Es findet keine Überprüfung statt. } //Quaternion Konstruktoren function gluQuaternion(const W, X, Y, Z: Single): TgluQuaternion; function gluQuaternionNormalize(const q: TgluQuaternion): TgluQuaternion; procedure gluQuaternionNormalizeInplace(var q: TgluQuaternion); function gluQuaternionToVector(const q: TgluQuaternion): TgluVector3f; function gluVectorToQuaternion(const v: TgluVector3f): TgluQuaternion; //Arithmetic function gluQuaternionConjugate(const q: TgluQuaternion): TgluQuaternion; function gluQuaternionMultiply(const l,r: TgluQuaternion): TgluQuaternion; function gluQuaternionAdd(const a,b: TgluQuaternion): TgluQuaternion; function gluQuaternionSubtract(const l,r: TgluQuaternion): TgluQuaternion; function gluQuaternionScale(const q: TgluQuaternion; const f: Single): TgluQuaternion; //To/From RotationMatrix function gluQuaternionToMatrix(const q: TgluQuaternion): TgluMatrix4f; function gluMatrixToQuaternion(const m: TgluMatrix4f): TgluQuaternion; //To/From Axis/Angle {WINKEL IN °DEG} function gluQuaternionToRotation(const q: TgluQuaternion; out angle: Single): TgluVector3f; function gluRotationToQuaternion(const angle: Single; const axis: TgluVector3f): TgluQuaternion; //Transforms function gluQuaternionTransformVec(const q: TgluQuaternion; const v: TgluVector3f): TgluVector3f; function gluQuaternionLookAt(const Location, Target, UpVector: TgluVector3f): TgluQuaternion; //Rotation zw. Richtungen: Wie muss a modifiziert werden um b zu bekommen? function gluVectorRotationTo(const a, b: TgluVector3f): TgluQuaternion; //Modifying Quaternions function gluQuaternionHalfAngle(const q: TgluQuaternion): TgluQuaternion; function gluQuaternionAngleBetween(const a, b: TgluQuaternion): double; function gluQuaternionSlerpOrientation(const a, b: TgluQuaternion; const t: single): TgluQuaternion; function gluQuaternionNlerpOrientation(const a, b: TgluQuaternion; const t: single): TgluQuaternion; operator +(const a, b: TgluQuaternion): TgluQuaternion; operator -(const l, r: TgluQuaternion): TgluQuaternion; operator *(const l, r: TgluQuaternion): TgluQuaternion; operator *(const q: TgluQuaternion; const s: Single): TgluQuaternion; const quW = 0; quX = 1; quY = 2; quZ = 3; gluQuaternionIdentity: TgluQuaternion = (1,0,0,0); implementation uses Math; operator +(const a, b: TgluQuaternion): TgluQuaternion; begin result := gluQuaternionAdd(a, b); end; operator -(const l, r: TgluQuaternion): TgluQuaternion; begin result := gluQuaternionSubtract(l, r); end; operator *(const l, r: TgluQuaternion): TgluQuaternion; begin result := gluQuaternionMultiply(l, r); end; operator *(const q: TgluQuaternion; const s: Single): TgluQuaternion; begin result := gluQuaternionScale(q, s); end; function gluQuaternion(const W, X, Y, Z: Single): TgluQuaternion; begin Result:= gluVector4f(W,X,Y,Z); end; function gluQuaternionNormalize(const q: TgluQuaternion): TgluQuaternion; begin Result:= q; gluQuaternionNormalizeInplace(Result); end; procedure gluQuaternionNormalizeInplace(var q: TgluQuaternion); var s: Double; begin s:= sqr(q[quX])+sqr(q[quY])+sqr(q[quZ])+sqr(q[quW]); // already normalized? if IsZero(s - 1) then exit; s:= 1/sqrt(s); q[quX]:= q[quX] * s; q[quY]:= q[quY] * s; q[quZ]:= q[quZ] * s; q[quW]:= q[quW] * s; end; function gluQuaternionToVector(const q: TgluQuaternion): TgluVector3f; begin Result:= gluVector3f(q[quX], q[quY], q[quZ]); end; function gluVectorToQuaternion(const v: TgluVector3f): TgluQuaternion; begin Result:= gluQuaternion(0, v[0], v[1], v[2]); end; function gluQuaternionConjugate(const q: TgluQuaternion): TgluQuaternion; begin Result[quW] := q[quW]; Result[quX] := -q[quX]; Result[quY] := -q[quY]; Result[quZ] := -q[quZ]; end; function gluQuaternionMultiply(const l, r: TgluQuaternion): TgluQuaternion; begin Result[quW] := -l[qux] * r[qux] - l[quy] * r[quy] - l[quz] * r[quz] + l[quw] * r[quw]; Result[quX] := l[qux] * r[quw] + l[quy] * r[quz] - l[quz] * r[quy] + l[quw] * r[qux]; Result[quY] := -l[qux] * r[quz] + l[quy] * r[quw] + l[quz] * r[qux] + l[quw] * r[quy]; Result[quZ] := l[qux] * r[quy] - l[quy] * r[qux] + l[quz] * r[quw] + l[quw] * r[quz]; end; function gluQuaternionAdd(const a, b: TgluQuaternion): TgluQuaternion; begin Result[quW] := a[quW] + b[quW]; Result[quX] := a[quX] + b[quX]; Result[quY] := a[quY] + b[quY]; Result[quZ] := a[quZ] + b[quZ]; end; function gluQuaternionSubtract(const l, r: TgluQuaternion): TgluQuaternion; begin Result[quW] := l[quW] - r[quW]; Result[quX] := l[quX] - r[quX]; Result[quY] := l[quY] - r[quY]; Result[quZ] := l[quZ] - r[quZ]; end; function gluQuaternionScale(const q: TgluQuaternion; const f: Single): TgluQuaternion; begin Result[quW] := q[quW] * f; Result[quX] := q[quX] * f; Result[quY] := q[quY] * f; Result[quZ] := q[quZ] * f; end; // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm function gluQuaternionToMatrix(const q: TgluQuaternion): TgluMatrix4f; var qx,qy,qz,qw: Single; begin qw:= q[quW]; qx:= q[quX]; qy:= q[quY]; qz:= q[quZ]; Result:= gluMatrixIdentity; Result[maAxisX] := gluVector4f( 1 - 2*SQR(qy) - 2*SQR(qz), 2*qx*qy + 2*qz*qw, 2*qx*qz - 2*qy*qw, 0); Result[maAxisY] := gluVector4f( 2*qx*qy - 2*qz*qw, 1 - 2*SQR(qx) - 2*SQR(qz), 2*qy*qz + 2*qx*qw, 0); Result[maAxisZ] := gluVector4f( 2*qx*qz + 2*qy*qw, 2*qy*qz - 2*qx*qw, 1 - 2*SQR(qx) - 2*SQR(qy), 0); end; // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm function gluMatrixToQuaternion(const m: TgluMatrix4f): TgluQuaternion; var trace, s: double; q: TgluQuaternion; begin trace := m[0][0] + m[1][1] + m[2][2]; // I removed + 1.0f; see discussion with Ethan if( trace > 0 ) then begin// I changed M_EPSILON to 0 s := 0.5 / SQRT(trace+ 1.0); q[quW] := 0.25 / s; q[quX] := ( m[2][1] - m[1][2] ) * s; q[quY] := ( m[0][2] - m[2][0] ) * s; q[quZ] := ( m[1][0] - m[0][1] ) * s; end else begin if ( m[0][0] > m[1][1]) and (m[0][0] > m[2][2] ) then begin s := 2.0 * SQRT( 1.0 + m[0][0] - m[1][1] - m[2][2]); q[quW] := (m[2][1] - m[1][2] ) / s; q[quX] := 0.25 * s; q[quY] := (m[0][1] + m[1][0] ) / s; q[quZ] := (m[0][2] + m[2][0] ) / s; end else if (m[1][1] > m[2][2]) then begin s := 2.0 * SQRT( 1.0 + m[1][1] - m[0][0] - m[2][2]); q[quW] := (m[0][2] - m[2][0] ) / s; q[quX] := (m[0][1] + m[1][0] ) / s; q[quY] := 0.25 * s; q[quZ] := (m[1][2] + m[2][1] ) / s; end else begin s := 2.0 * SQRT( 1.0 + m[2][2] - m[0][0] - m[1][1] ); q[quW] := (m[1][0] - m[0][1] ) / s; q[quX] := (m[0][2] + m[2][0] ) / s; q[quY] := (m[1][2] + m[2][1] ) / s; q[quZ] := 0.25 * s; end; end; Result:= q; end; // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm function gluQuaternionToRotation(const q: TgluQuaternion; out angle: Single): TgluVector3f; var s: double; begin angle := radtodeg(2 * arccos(q[quW])); s := sqrt(1-q[quW]*q[quW]); // assuming quaternion normalised then w is less than 1, so term always positive. if (s < 0.001) then begin // test to avoid divide by zero, s is always positive due to sqrt // if s close to zero then direction of axis not important Result[0] := q[quX]; // if it is important that axis is normalised then replace with x=1; y=z=0; Result[1] := q[quY]; Result[2] := q[quZ]; end else begin Result[0] := q[quX] / s; // normalise axis Result[1] := q[quY] / s; Result[2] := q[quZ] / s; end; end; function gluRotationToQuaternion(const angle: Single; const axis: TgluVector3f): TgluQuaternion; var a: single; begin a:= degtorad(angle) / 2; Result:= gluQuaternion( cos(a), sin(a) * axis[0], sin(a) * axis[1], sin(a) * axis[2]); end; // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/index.htm function gluQuaternionTransformVec(const q: TgluQuaternion; const v: TgluVector3f): TgluVector3f; var p: TgluQuaternion; begin //Pout = q * Pin * q' p:= gluQuaternionMultiply(q, gluVectorToQuaternion(v)); p:= gluQuaternionMultiply(p, gluQuaternionConjugate(q)); Result:= gluQuaternionToVector(p); end; // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/halfAngle.htm function gluQuaternionHalfAngle(const q: TgluQuaternion): TgluQuaternion; begin Result:= q; Result[quW]:= Result[quW] + 1; gluQuaternionNormalizeInplace(Result); end; function gluQuaternionAngleBetween(const a, b: TgluQuaternion): double; var cosHalfTheta: double; begin cosHalfTheta:= a[quW] * b[quW] + a[quX] * b[quX] + a[quY] * b[quY] + a[quZ] * b[quZ]; Result:= arccos(cosHalfTheta) * 2; end; // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm function gluQuaternionSlerpOrientation(const a, b: TgluQuaternion; const t: single): TgluQuaternion; var qa,qb: TgluQuaternion; cosHalfTheta, sinHalfTheta, halfTheta, ratioA, ratioB: double; begin qa:= a; qb:= b; // Calculate angle between them. cosHalfTheta:= a[quW] * b[quW] + a[quX] * b[quX] + a[quY] * b[quY] + a[quZ] * b[quZ]; if (cosHalfTheta < 0) then begin qb:= gluQuaternion( -b[quW], -b[quX], -b[quY], b[quZ] ); cosHalfTheta:= -cosHalfTheta; end; // if qa=qb or qa=-qb then theta = 0 and we can return qa if abs(cosHalfTheta) >= 1.0 then begin Result:= qa; Exit; end; // Calculate temporary values. halfTheta := arccos(cosHalfTheta); sinHalfTheta := sqrt(1.0 - sqr(cosHalfTheta)); // if theta = 180 degrees then result is not fully defined // we could rotate around any axis normal to qa or qb if (abs(sinHalfTheta) < 0.001) then begin Result:= gluQuaternionAdd(gluQuaternionScale(qa, 0.5), gluQuaternionScale(qb, 0.5)); exit end; ratioA := sin((1 - t) * halfTheta) / sinHalfTheta; ratioB := sin(t * halfTheta) / sinHalfTheta; //calculate Quaternion. Result:= gluQuaternionAdd(gluQuaternionScale(qa, ratioA), gluQuaternionScale(qb, ratioB)); end; function gluQuaternionNlerpOrientation(const a, b: TgluQuaternion; const t: single): TgluQuaternion; begin Result:= gluQuaternionAdd(a, gluQuaternionScale(gluQuaternionSubtract(b,a), t)); gluQuaternionNormalizeInplace(Result); end; function gluQuaternionLookAt(const Location, Target, UpVector: TgluVector3f): TgluQuaternion; var front, up, right: TgluVector3f; w4_recip: Single; begin front:= gluVectorSubtract(Location, Target); // eigentlich falschrum. don't ask. up:= UpVector; gluVectorOrthoNormalize(front, up); right:= gluVectorProduct(up, front); Result[quW]:= SQRT(1 + right[0] + up[1] + front[2]) * 0.5; w4_recip:= 1 / (4 * Result[quW]); Result[quX]:= (front[1] - up[2]) * w4_recip; Result[quY]:= (right[2] - front[0]) * w4_recip; Result[quZ]:= (up[0] - right[1]) * w4_recip; end; function gluVectorRotationTo(const a, b: TgluVector3f): TgluQuaternion; var d, qw: single; ax: TgluVector3f; begin d:=gluVectorScalar(a, b); ax:= gluVectorProduct(a, b); qw:= gluVectorLength(a) * gluVectorLength(b) + d; if (qw < 0.0001) then begin // vectors are 180 degrees apart Result:= gluQuaternion(0, -a[2],a[1],a[0]); end else begin Result:= gluQuaternion(qw, ax[0],ax[1],ax[2]); end; gluQuaternionNormalizeInplace(Result); end; end.