1 /* 2 Copyright (c) 2011-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Quaternions 31 * 32 * Copyright: Timur Gafarov 2011-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov 35 */ 36 module dlib.math.quaternion; 37 38 import std.math; 39 import std.traits; 40 41 import dlib.math.vector; 42 import dlib.math.matrix; 43 import dlib.math.utils; 44 45 /** 46 * Quaternion representation 47 */ 48 struct Quaternion(T) 49 { 50 Vector!(T,4) vectorof; 51 alias vectorof this; 52 53 this(T x, T y, T z, T w) 54 { 55 vectorof = Vector!(T,4)(x, y, z, w); 56 } 57 58 this(T[4] arr) 59 { 60 vectorof.arrayof = arr; 61 } 62 63 this(Vector!(T,4) v) 64 { 65 vectorof = v; 66 } 67 68 this(Vector!(T,3) v, T neww) 69 { 70 vectorof = Vector!(T,4)(v.x, v.y, v.z, neww); 71 } 72 73 /** 74 * Identity quaternion 75 */ 76 static Quaternion!(T) identity() 77 { 78 return Quaternion!(T)(T(0), T(0), T(0), T(1)); 79 } 80 81 /** 82 * Quaternion!(T) + Quaternion!(T) 83 */ 84 Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "+") 85 { 86 return Quaternion!(T)(x + q.x, y + q.y, z + q.z, w + q.w); 87 } 88 89 /** 90 * Quaternion!(T) += Quaternion!(T) 91 */ 92 Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "+") 93 { 94 this = this + q; 95 return this; 96 } 97 98 /** 99 * Quaternion!(T) - Quaternion!(T) 100 */ 101 Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "-") 102 { 103 return Quaternion!(T)(x - q.x, y - q.y, z - q.z, w - q.w); 104 } 105 106 /** 107 * Quaternion!(T) -= Quaternion!(T) 108 */ 109 Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "-") 110 { 111 this = this - q; 112 return this; 113 } 114 115 /** 116 * Quaternion!(T) * Quaternion!(T) 117 */ 118 Quaternion!(T) opBinary(string op)(Quaternion!(T) q) if (op == "*") 119 { 120 return Quaternion!(T) 121 ( 122 (x * q.w) + (w * q.x) + (y * q.z) - (z * q.y), 123 (y * q.w) + (w * q.y) + (z * q.x) - (x * q.z), 124 (z * q.w) + (w * q.z) + (x * q.y) - (y * q.x), 125 (w * q.w) - (x * q.x) - (y * q.y) - (z * q.z) 126 ); 127 } 128 129 /** 130 * Quaternion!(T) *= Quaternion!(T) 131 */ 132 Quaternion!(T) opOpAssign(string op)(Quaternion!(T) q) if (op == "*") 133 { 134 this = this * q; 135 return this; 136 } 137 138 /** 139 * Quaternion!(T) * T 140 */ 141 Quaternion!(T) opBinary(string op)(T k) if (op == "*") 142 { 143 return Quaternion!(T)(x * k, y * k, z * k, w * k); 144 } 145 146 /** 147 * Quaternion!(T) *= T 148 */ 149 Quaternion!(T) opOpAssign(string op)(T k) if (op == "*") 150 { 151 x *= k; 152 y *= k; 153 z *= k; 154 w *= k; 155 return this; 156 } 157 158 /** 159 * T * Quaternion!(T) 160 */ 161 Quaternion!(T) opBinaryRight(string op) (T k) if (op == "*") 162 { 163 return Quaternion!(T)(x * k, y * k, z * k, w * k); 164 } 165 166 /** 167 * Quaternion!(T) / T 168 */ 169 Quaternion!(T) opBinary(string op)(T k) if (op == "/") 170 { 171 T oneOverK = 1.0 / k; 172 return Quaternion!(T) 173 ( 174 x * oneOverK, 175 y * oneOverK, 176 z * oneOverK, 177 w * oneOverK 178 ); 179 } 180 181 /** 182 * Quaternion!(T) /= T 183 */ 184 Quaternion!(T) opOpAssign(string op)(T k) if (op == "/") 185 { 186 T oneOverK = 1.0 / k; 187 x *= oneOverK; 188 y *= oneOverK; 189 z *= oneOverK; 190 w *= oneOverK; 191 return this; 192 } 193 194 /** 195 * Quaternion!(T) * Vector!(T,3) 196 */ 197 Quaternion!(T) opBinary(string op)(Vector!(T,3) v) if (op == "*") 198 { 199 return Quaternion!(T) 200 ( 201 (w * v.x) + (y * v.z) - (z * v.y), 202 (w * v.y) + (z * v.x) - (x * v.z), 203 (w * v.z) + (x * v.y) - (y * v.x), 204 - (x * v.x) - (y * v.y) - (z * v.z) 205 ); 206 } 207 208 /** 209 * Quaternion!(T) *= Vector!(T,3) 210 */ 211 Quaternion!(T) opOpAssign(string op)(Vector!(T,3) v) if (op == "*") 212 { 213 this = this * v; 214 return this; 215 } 216 217 /** 218 * Conjugate 219 * A quaternion with the opposite rotation 220 */ 221 Quaternion!(T) conjugate() 222 { 223 return Quaternion!(T)(-x, -y, -z, w); 224 } 225 226 alias conj = conjugate; 227 228 /** 229 * Compute the W component of a unit length quaternion 230 */ 231 void computeW() 232 { 233 T t = T(1.0) - (x * x) - (y * y) - (z * z); 234 if (t < 0.0) 235 w = 0.0; 236 else 237 w = -(t.sqrt); 238 } 239 240 /** 241 * Rotate a point by quaternion 242 */ 243 Vector!(T,3) rotate(Vector!(T,3) v) 244 { 245 Quaternion!(T) qf = this * v * this.conj; 246 return Vector!(T,3)(qf.x, qf.y, qf.z); 247 } 248 249 Vector!(T,3) opBinaryRight(string op) (Vector!(T,3) v) if (op == "*") 250 { 251 Quaternion!(T) qf = this * v * this.conj; 252 return Vector!(T,3)(qf.x, qf.y, qf.z); 253 } 254 255 static if (isNumeric!(T)) 256 { 257 /** 258 * Normalized version 259 */ 260 Quaternion!(T) normalized() 261 { 262 Quaternion!(T) q = this; 263 q.normalize(); 264 return q; 265 } 266 267 /** 268 * Convert to 4x4 matrix 269 */ 270 Matrix!(T,4) toMatrix4x4() 271 { 272 auto mat = Matrix!(T,4).identity; 273 274 mat[0] = 1.0 - 2.0 * (y * y + z * z); 275 mat[1] = 2.0 * (x * y + z * w); 276 mat[2] = 2.0 * (x * z - y * w); 277 mat[3] = 0.0; 278 279 mat[4] = 2.0 * (x * y - z * w); 280 mat[5] = 1.0 - 2.0 * (x * x + z * z); 281 mat[6] = 2.0 * (z * y + x * w); 282 mat[7] = 0.0; 283 284 mat[8] = 2.0 * (x * z + y * w); 285 mat[9] = 2.0 * (y * z - x * w); 286 mat[10] = 1.0 - 2.0 * (x * x + y * y); 287 mat[11] = 0.0; 288 289 mat[12] = 0.0; 290 mat[13] = 0.0; 291 mat[14] = 0.0; 292 mat[15] = 1.0; 293 294 return mat; 295 } 296 297 /** 298 * Convert to 3x3 matrix 299 */ 300 Matrix!(T,3) toMatrix3x3() 301 { 302 auto mat = Matrix!(T,3).identity; 303 304 mat[0] = 1.0 - 2.0 * (y * y + z * z); 305 mat[1] = 2.0 * (x * y + z * w); 306 mat[2] = 2.0 * (x * z - y * w); 307 308 mat[3] = 2.0 * (x * y - z * w); 309 mat[4] = 1.0 - 2.0 * (x * x + z * z); 310 mat[5] = 2.0 * (z * y + x * w); 311 312 mat[6] = 2.0 * (x * z + y * w); 313 mat[7] = 2.0 * (y * z - x * w); 314 mat[8] = 1.0 - 2.0 * (x * x + y * y); 315 316 return mat; 317 } 318 319 /** 320 * Setup the quaternion to perform a rotation, 321 * given the angular displacement in matrix form 322 */ 323 static Quaternion!(T) fromMatrix(Matrix!(T,4) m) 324 { 325 Quaternion!(T) q; 326 327 T trace = m.a11 + m.a22 + m.a33 + 1.0; 328 329 if (trace > 0.0001) 330 { 331 T s = 0.5 / sqrt(trace); 332 q.w = 0.25 / s; 333 q.x = (m.a23 - m.a32) * s; 334 q.y = (m.a31 - m.a13) * s; 335 q.z = (m.a12 - m.a21) * s; 336 } 337 else 338 { 339 if ((m.a11 > m.a22) && (m.a11 > m.a33)) 340 { 341 T s = 0.5 / sqrt(1.0 + m.a11 - m.a22 - m.a33); 342 q.x = 0.25 / s; 343 q.y = (m.a21 + m.a12) * s; 344 q.z = (m.a31 + m.a13) * s; 345 q.w = (m.a32 - m.a23) * s; 346 } 347 else if (m.a22 > m.a33) 348 { 349 T s = 0.5 / sqrt(1.0 + m.a22 - m.a11 - m.a33); 350 q.x = (m.a21 + m.a12) * s; 351 q.y = 0.25 / s; 352 q.z = (m.a32 + m.a23) * s; 353 q.w = (m.a31 - m.a13) * s; 354 } 355 else 356 { 357 T s = 0.5 / sqrt(1.0 + m.a33 - m.a11 - m.a22); 358 q.x = (m.a31 + m.a13) * s; 359 q.y = (m.a32 + m.a23) * s; 360 q.z = 0.25 / s; 361 q.w = (m.a21 - m.a12) * s; 362 } 363 } 364 365 return q; 366 } 367 368 /** 369 * Setup the quaternion to perform a rotation, 370 * given the orientation in XYZ-Euler angles format (in radians) 371 */ 372 static Quaternion!(T) fromEulerAngles(Vector!(T,3) e) 373 { 374 Quaternion!(T) q; 375 376 T sr = sin(e.x * 0.5); 377 T cr = cos(e.x * 0.5); 378 T sp = sin(e.y * 0.5); 379 T cp = cos(e.y * 0.5); 380 T sy = sin(e.z * 0.5); 381 T cy = cos(e.z * 0.5); 382 383 q.w = (cy * cp * cr) + (sy * sp * sr); 384 q.x = -(sy * sp * cr) + (cy * cp * sr); 385 q.y = (cy * sp * cr) + (sy * cp * sr); 386 q.z = -(cy * sp * sr) + (sy * cp * cr); 387 388 return q; 389 } 390 391 /** 392 * Setup the Euler angles, given a rotation Quaternion. 393 * Returned x,y,z are in radians 394 */ 395 Vector!(T,3) toEulerAngles() 396 { 397 Vector!(T,3) e; 398 399 e.y = asin(2.0 * ((x * z) + (w * y))); 400 401 T cy = cos(e.y); 402 T oneOverCosY = 1.0 / cy; 403 404 if (fabs(cy) > 0.001) 405 { 406 e.x = atan2(2.0 * ((w * x) - (y * z)) * oneOverCosY, 407 (1.0 - 2.0 * (x*x + y*y)) * oneOverCosY); 408 e.z = atan2(2.0 * ((w * z) - (x * y)) * oneOverCosY, 409 (1.0 - 2.0 * (y*y + z*z)) * oneOverCosY); 410 } 411 else 412 { 413 e.x = 0.0; 414 e.z = atan2(2.0 * ((x * y) + (w * z)), 415 1.0 - 2.0 * (x*x + z*z)); 416 } 417 418 return e; 419 } 420 421 /** 422 * Return the rotation angle (in radians) 423 */ 424 T rotationAngle() 425 { 426 return 2.0 * acos(w); 427 } 428 429 /** 430 * Return the rotation axis 431 */ 432 Vector!(T,3) rotationAxis() 433 { 434 T s = sqrt(1.0 - (w * w)); 435 436 if (s <= 0.0f) 437 return Vector!(T,3)(x, y, z); 438 else 439 return Vector!(T,3)(x / s, y / s, z / s); 440 } 441 442 /** 443 * Quaternion as an angular velocity 444 */ 445 Vector!(T,3) generator() 446 { 447 T s = sqrt(1.0 - (w * w)); 448 449 Vector!(T,3) axis; 450 451 if (s <= 0.0) 452 axis = Vector!(T,3)(x, y, z); 453 else 454 axis = Vector!(T,3)(x * s, y * s, z * s); 455 456 T angle = 2.0 * atan2(s, w); 457 458 return axis * angle; 459 } 460 } 461 } 462 463 /* 464 * Predefined quaternion type aliases 465 */ 466 /// Alias for single precision Quaternion 467 alias Quaternionf = Quaternion!(float); 468 /// Alias for double precision Quaternion 469 alias Quaterniond = Quaternion!(double); 470 471 /// 472 unittest 473 { 474 Quaternionf q1 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f); 475 Vector3f v1 = q1.rotate(Vector3f(1.0f, 0.0f, 0.0f)); 476 assert(isAlmostZero(v1 - Vector3f(1.0f, 0.0f, 0.0f))); 477 478 Quaternionf q2 = Quaternionf.identity; 479 assert(isConsiderZero(q2.x)); 480 assert(isConsiderZero(q2.y)); 481 assert(isConsiderZero(q2.z)); 482 assert(isConsiderZero(q2.w - 1.0f)); 483 484 Quaternionf q3 = Quaternionf([1.0f, 0.0f, 0.0f, 1.0f]); 485 Quaternionf q4 = Quaternionf([0.0f, 1.0f, 0.0f, 1.0f]); 486 q4 = q3 * q4; 487 assert(q4 == Quaternionf(1, 1, 1, 1)); 488 489 Vector3f v2 = Vector3f(0, 0, 1); 490 Quaternionf q5 = Quaternionf(v2, 1.0f); 491 q5 *= q5; 492 assert(q5 == Quaternionf(0, 0, 2, 0)); 493 494 Quaternionf q6 = Quaternionf(Vector4f(1, 0, 0, 1)); 495 Quaternionf q7 = q6 + q6 - Quaternionf(2, 0, 0, 2); 496 assert(q7 == Quaternionf(0, 0, 0, 0)); 497 498 Quaternionf q8 = Quaternionf(0.5f, 0.5f, 0.5f, 0.0f); 499 q8.computeW(); 500 assert(q8.w == -0.5f); 501 502 Quaternionf q9 = Quaternionf(0.5f, 0.0f, 0.0f, 0.0f); 503 q9 = q9.normalized; 504 assert(q9 == Quaternionf(1, 0, 0, 0)); 505 506 Quaternionf q10 = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f); 507 Matrix4f m1 = q10.toMatrix4x4; 508 assert(m1 == matrixf( 509 1, 0, 0, 0, 510 0, 1, 0, 0, 511 0, 0, 1, 0, 512 0, 0, 0, 1) 513 ); 514 515 Matrix3f m2 = q10.toMatrix3x3; 516 assert(m2 == matrixf( 517 1, 0, 0, 518 0, 1, 0, 519 0, 0, 1) 520 ); 521 } 522 523 /** 524 * Setup a quaternion to rotate about world axis. 525 * Theta must be in radians 526 */ 527 Quaternion!(T) rotationQuaternion(T)(uint rotaxis, T theta) 528 { 529 Quaternion!(T) res = Quaternion!(T).identity; 530 T thetaOver2 = theta * 0.5; 531 532 switch (rotaxis) 533 { 534 case Axis.x: 535 res.w = cos(thetaOver2); 536 res.x = sin(thetaOver2); 537 res.y = 0.0; 538 res.z = 0.0; 539 break; 540 541 case Axis.y: 542 res.w = cos(thetaOver2); 543 res.x = 0.0; 544 res.y = sin(thetaOver2); 545 res.z = 0.0; 546 break; 547 548 case Axis.z: 549 res.w = cos(thetaOver2); 550 res.x = 0.0; 551 res.y = 0.0; 552 res.z = sin(thetaOver2); 553 break; 554 555 default: 556 assert(0); 557 } 558 559 return res; 560 } 561 562 /** 563 * Setup a quaternion to rotate about specified axis. 564 * Theta must be in radians 565 */ 566 Quaternion!(T) rotationQuaternion(T)(Vector!(T,3) rotaxis, T theta) 567 { 568 Quaternion!(T) res; 569 570 T thetaOver2 = theta * 0.5; 571 T sinThetaOver2 = sin(thetaOver2); 572 573 res.w = cos(thetaOver2); 574 res.x = rotaxis.x * sinThetaOver2; 575 res.y = rotaxis.y * sinThetaOver2; 576 res.z = rotaxis.z * sinThetaOver2; 577 return res; 578 } 579 580 /** 581 * Setup a quaternion to represent rotation 582 * between two unit-length vectors 583 */ 584 Quaternion!(T) rotationBetween(T)(Vector!(T,3) a, Vector!(T,3) b) 585 { 586 Quaternion!(T) q; 587 588 float d = dot(a, b); 589 float angle = acos(d); 590 591 Vector!(T,3) axis; 592 if (d < -0.9999) 593 { 594 Vector!(T,3) c; 595 if (a.y != 0.0 || a.z != 0.0) 596 c = Vector!(T,3)(1, 0, 0); 597 else 598 c = Vector!(T,3)(0, 1, 0); 599 axis = cross(a, c); 600 axis.normalize(); 601 q = rotationQuaternion(axis, angle); 602 } 603 else if (d > 0.9999) 604 { 605 q = Quaternion!(T).identity; 606 } 607 else 608 { 609 axis = cross(a, b); 610 axis.normalize(); 611 q = rotationQuaternion(axis, angle); 612 } 613 614 return q; 615 } 616 617 /** 618 * Quaternion logarithm 619 */ 620 Quaternion!(T) log(T)(Quaternion!(T) q) 621 { 622 Quaternion!(T) res; 623 res.w = 0.0; 624 625 if (fabs(q.w) < 1.0) 626 { 627 T theta = acos(q.w); 628 T sin_theta = sin(theta); 629 630 if (fabs(sin_theta) > 0.00001) 631 { 632 T thetaOverSinTheta = theta / sin_theta; 633 res.x = q.x * thetaOverSinTheta; 634 res.y = q.y * thetaOverSinTheta; 635 res.z = q.z * thetaOverSinTheta; 636 return res; 637 } 638 } 639 640 res.x = q.x; 641 res.y = q.y; 642 res.z = q.z; 643 return res; 644 } 645 646 /** 647 * Quaternion exponential 648 */ 649 Quaternion!(T) exp(T) (Quaternion!(T) q) 650 { 651 T theta = sqrt(dot(q, q)); 652 T sin_theta = sin(theta); 653 Quaternion!(T) res; 654 res.w = cos(theta); 655 656 if (fabs(sin_theta) > 0.00001) 657 { 658 T sinThetaOverTheta = sin_theta / theta; 659 res.x = q.x * sinThetaOverTheta; 660 res.y = q.y * sinThetaOverTheta; 661 res.z = q.z * sinThetaOverTheta; 662 } 663 else 664 { 665 res.x = q.x; 666 res.y = q.y; 667 res.z = q.z; 668 } 669 670 return res; 671 } 672 673 /** 674 * Quaternion exponentiation 675 */ 676 Quaternion!(T) pow(T) (Quaternion!(T) q, T exponent) 677 { 678 if (fabs(q.w) > 0.9999) 679 return q; 680 T alpha = acos(q.w); 681 T newAlpha = alpha * exponent; 682 Vector!(T,3) n = Vector!(T,3)(q.x, q.y, q.z); 683 n *= sin(newAlpha) / sin(alpha); 684 return new Quaternion!(T)(n, cos(newAlpha)); 685 } 686 687 /** 688 * Spherical linear interpolation 689 */ 690 Quaternion!(T) slerp(T)( 691 Quaternion!(T) q0, 692 Quaternion!(T) q1, 693 T t) 694 { 695 if (t <= 0.0) return q0; 696 if (t >= 1.0) return q1; 697 698 T cosOmega = dot(q0, q1); 699 T q1w = q1.w; 700 T q1x = q1.x; 701 T q1y = q1.y; 702 T q1z = q1.z; 703 704 if (cosOmega < 0.0) 705 { 706 q1w = -q1w; 707 q1x = -q1x; 708 q1y = -q1y; 709 q1z = -q1z; 710 cosOmega = -cosOmega; 711 } 712 assert (cosOmega < 1.1); 713 714 T k0, k1; 715 if (cosOmega > 0.9999) 716 { 717 k0 = 1.0 - t; 718 k1 = t; 719 } 720 else 721 { 722 T sinOmega = sqrt(1.0 - (cosOmega * cosOmega)); 723 T omega = atan2(sinOmega, cosOmega); 724 T oneOverSinOmega = 1.0 / sinOmega; 725 k0 = sin((1.0 - t) * omega) * oneOverSinOmega; 726 k1 = sin(t * omega) * oneOverSinOmega; 727 } 728 729 Quaternion!(T) res = Quaternion!(T) 730 ( 731 (k0 * q0.x) + (k1 * q1x), 732 (k0 * q0.y) + (k1 * q1y), 733 (k0 * q0.z) + (k1 * q1z), 734 (k0 * q0.w) + (k1 * q1w) 735 ); 736 return res; 737 } 738 739 /** 740 * Spherical cubic interpolation 741 */ 742 Quaternion!(T) squad(T)( 743 Quaternion!(T) q0, 744 Quaternion!(T) qa, 745 Quaternion!(T) qb, 746 Quaternion!(T) q1, 747 T t) 748 { 749 T slerp_t = 2.0 * t * (1.0 - t); 750 Quaternion!(T) slerp_q0 = slerp(q0, q1, t); 751 Quaternion!(T) slerp_q1 = slerp(qa, qb, t); 752 return slerp(slerp_q0, slerp_q1, slerp_t); 753 } 754 755 /** 756 * Compute intermediate quaternions for building spline segments 757 */ 758 Quaternion!(T) intermediate(T)( 759 Quaternion!(T) qprev, 760 Quaternion!(T) qcurr, 761 Quaternion!(T) qnext, 762 ref Quaternion!(T) qa, 763 ref Quaternion!(T) qb) 764 in 765 { 766 assert (dot(qprev, qprev) <= 1.0001); 767 assert (dot(qcurr, qcurr) <= 1.0001); 768 } 769 do 770 { 771 Quaternion!(T) inv_prev = qprev.conj; 772 Quaternion!(T) inv_curr = qcurr.conj; 773 774 Quaternion!(T) p0 = inv_prev * qcurr; 775 Quaternion!(T) p1 = inv_curr * qnext; 776 777 Quaternion!(T) arg = (log(p0) - log(p1)) * 0.25; 778 779 qa = qcurr * exp( arg); 780 qb = qcurr * exp(-arg); 781 }