1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #ifndef __IRR_QUATERNION_H_INCLUDED__
\r
6 #define __IRR_QUATERNION_H_INCLUDED__
\r
8 #include "irrTypes.h"
\r
10 #include "matrix4.h"
\r
11 #include "vector3d.h"
\r
13 // NOTE: You *only* need this when updating an application from Irrlicht before 1.8 to Irrlicht 1.8 or later.
\r
14 // Between Irrlicht 1.7 and Irrlicht 1.8 the quaternion-matrix conversions changed.
\r
15 // Before the fix they had mixed left- and right-handed rotations.
\r
16 // To test if your code was affected by the change enable IRR_TEST_BROKEN_QUATERNION_USE and try to compile your application.
\r
17 // This defines removes those functions so you get compile errors anywhere you use them in your code.
\r
18 // For every line with a compile-errors you have to change the corresponding lines like that:
\r
19 // - When you pass the matrix to the quaternion constructor then replace the matrix by the transposed matrix.
\r
20 // - For uses of getMatrix() you have to use quaternion::getMatrix_transposed instead.
\r
21 // #define IRR_TEST_BROKEN_QUATERNION_USE
\r
28 //! Quaternion class for representing rotations.
\r
29 /** It provides cheap combinations and avoids gimbal locks.
\r
30 Also useful for interpolations. */
\r
35 //! Default Constructor
\r
36 quaternion() : X(0.0f), Y(0.0f), Z(0.0f), W(1.0f) {}
\r
39 quaternion(f32 x, f32 y, f32 z, f32 w) : X(x), Y(y), Z(z), W(w) { }
\r
41 //! Constructor which converts Euler angles (radians) to a quaternion
\r
42 quaternion(f32 x, f32 y, f32 z);
\r
44 //! Constructor which converts Euler angles (radians) to a quaternion
\r
45 quaternion(const vector3df& vec);
\r
47 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
48 //! Constructor which converts a matrix to a quaternion
\r
49 quaternion(const matrix4& mat);
\r
52 //! Equality operator
\r
53 bool operator==(const quaternion& other) const;
\r
55 //! inequality operator
\r
56 bool operator!=(const quaternion& other) const;
\r
58 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
59 //! Matrix assignment operator
\r
60 inline quaternion& operator=(const matrix4& other);
\r
64 quaternion operator+(const quaternion& other) const;
\r
66 //! Multiplication operator
\r
67 //! Be careful, unfortunately the operator order here is opposite of that in CMatrix4::operator*
\r
68 quaternion operator*(const quaternion& other) const;
\r
70 //! Multiplication operator with scalar
\r
71 quaternion operator*(f32 s) const;
\r
73 //! Multiplication operator with scalar
\r
74 quaternion& operator*=(f32 s);
\r
76 //! Multiplication operator
\r
77 vector3df operator*(const vector3df& v) const;
\r
79 //! Multiplication operator
\r
80 quaternion& operator*=(const quaternion& other);
\r
82 //! Calculates the dot product
\r
83 inline f32 dotProduct(const quaternion& other) const;
\r
85 //! Sets new quaternion
\r
86 inline quaternion& set(f32 x, f32 y, f32 z, f32 w);
\r
88 //! Sets new quaternion based on Euler angles (radians)
\r
89 inline quaternion& set(f32 x, f32 y, f32 z);
\r
91 //! Sets new quaternion based on Euler angles (radians)
\r
92 inline quaternion& set(const core::vector3df& vec);
\r
94 //! Sets new quaternion from other quaternion
\r
95 inline quaternion& set(const core::quaternion& quat);
\r
97 //! returns if this quaternion equals the other one, taking floating point rounding errors into account
\r
98 inline bool equals(const quaternion& other,
\r
99 const f32 tolerance = ROUNDING_ERROR_f32 ) const;
\r
101 //! Normalizes the quaternion
\r
102 inline quaternion& normalize();
\r
104 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
105 //! Creates a matrix from this quaternion
\r
106 matrix4 getMatrix() const;
\r
108 //! Faster method to create a rotation matrix, you should normalize the quaternion before!
\r
109 void getMatrixFast(matrix4 &dest) const;
\r
111 //! Creates a matrix from this quaternion
\r
112 void getMatrix( matrix4 &dest, const core::vector3df &translation=core::vector3df() ) const;
\r
115 Creates a matrix from this quaternion
\r
116 Rotate about a center point
\r
118 core::quaternion q;
\r
119 q.rotationFromTo ( vin[i].Normal, forward );
\r
120 q.getMatrixCenter ( lookat, center, newPos );
\r
123 m2.setInverseTranslation ( center );
\r
127 m2.setTranslation ( newPos );
\r
131 void getMatrixCenter( matrix4 &dest, const core::vector3df ¢er, const core::vector3df &translation ) const;
\r
133 //! Creates a matrix from this quaternion
\r
134 inline void getMatrix_transposed( matrix4 &dest ) const;
\r
136 //! Inverts this quaternion
\r
137 quaternion& makeInverse();
\r
139 //! Set this quaternion to the linear interpolation between two quaternions
\r
140 /** NOTE: lerp result is *not* a normalized quaternion. In most cases
\r
141 you will want to use lerpN instead as most other quaternion functions expect
\r
142 to work with a normalized quaternion.
\r
143 \param q1 First quaternion to be interpolated.
\r
144 \param q2 Second quaternion to be interpolated.
\r
145 \param time Progress of interpolation. For time=0 the result is
\r
146 q1, for time=1 the result is q2. Otherwise interpolation
\r
147 between q1 and q2. Result is not normalized.
\r
149 quaternion& lerp(quaternion q1, quaternion q2, f32 time);
\r
151 //! Set this quaternion to the linear interpolation between two quaternions and normalize the result
\r
153 \param q1 First quaternion to be interpolated.
\r
154 \param q2 Second quaternion to be interpolated.
\r
155 \param time Progress of interpolation. For time=0 the result is
\r
156 q1, for time=1 the result is q2. Otherwise interpolation
\r
157 between q1 and q2. Result is normalized.
\r
159 quaternion& lerpN(quaternion q1, quaternion q2, f32 time);
\r
161 //! Set this quaternion to the result of the spherical interpolation between two quaternions
\r
162 /** \param q1 First quaternion to be interpolated.
\r
163 \param q2 Second quaternion to be interpolated.
\r
164 \param time Progress of interpolation. For time=0 the result is
\r
165 q1, for time=1 the result is q2. Otherwise interpolation
\r
167 \param threshold To avoid inaccuracies at the end (time=1) the
\r
168 interpolation switches to linear interpolation at some point.
\r
169 This value defines how much of the remaining interpolation will
\r
170 be calculated with lerp. Everything from 1-threshold up will be
\r
171 linear interpolation.
\r
173 quaternion& slerp(quaternion q1, quaternion q2,
\r
174 f32 time, f32 threshold=.05f);
\r
176 //! Set this quaternion to represent a rotation from angle and axis.
\r
177 /** Axis must be unit length.
\r
178 The quaternion representing the rotation is
\r
179 q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k).
\r
180 \param angle Rotation Angle in radians.
\r
181 \param axis Rotation axis. */
\r
182 quaternion& fromAngleAxis (f32 angle, const vector3df& axis);
\r
184 //! Fills an angle (radians) around an axis (unit vector)
\r
185 void toAngleAxis (f32 &angle, core::vector3df& axis) const;
\r
187 //! Output this quaternion to an Euler angle (radians)
\r
188 void toEuler(vector3df& euler) const;
\r
190 //! Set quaternion to identity
\r
191 quaternion& makeIdentity();
\r
193 //! Set quaternion to represent a rotation from one vector to another.
\r
194 quaternion& rotationFromTo(const vector3df& from, const vector3df& to);
\r
196 //! Quaternion elements.
\r
197 f32 X; // vectorial (imaginary) part
\r
200 f32 W; // real part
\r
204 // Constructor which converts Euler angles to a quaternion
\r
205 inline quaternion::quaternion(f32 x, f32 y, f32 z)
\r
211 // Constructor which converts Euler angles to a quaternion
\r
212 inline quaternion::quaternion(const vector3df& vec)
\r
214 set(vec.X,vec.Y,vec.Z);
\r
217 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
218 // Constructor which converts a matrix to a quaternion
\r
219 inline quaternion::quaternion(const matrix4& mat)
\r
226 inline bool quaternion::operator==(const quaternion& other) const
\r
228 return ((X == other.X) &&
\r
234 // inequality operator
\r
235 inline bool quaternion::operator!=(const quaternion& other) const
\r
237 return !(*this == other);
\r
240 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
241 // matrix assignment operator
\r
242 inline quaternion& quaternion::operator=(const matrix4& m)
\r
244 const f32 diag = m[0] + m[5] + m[10] + 1;
\r
248 const f32 scale = sqrtf(diag) * 2.0f; // get scale from diagonal
\r
250 // TODO: speed this up
\r
251 X = (m[6] - m[9]) / scale;
\r
252 Y = (m[8] - m[2]) / scale;
\r
253 Z = (m[1] - m[4]) / scale;
\r
258 if (m[0]>m[5] && m[0]>m[10])
\r
260 // 1st element of diag is greatest value
\r
261 // find scale according to 1st element, and double it
\r
262 const f32 scale = sqrtf(1.0f + m[0] - m[5] - m[10]) * 2.0f;
\r
264 // TODO: speed this up
\r
266 Y = (m[4] + m[1]) / scale;
\r
267 Z = (m[2] + m[8]) / scale;
\r
268 W = (m[6] - m[9]) / scale;
\r
270 else if (m[5]>m[10])
\r
272 // 2nd element of diag is greatest value
\r
273 // find scale according to 2nd element, and double it
\r
274 const f32 scale = sqrtf(1.0f + m[5] - m[0] - m[10]) * 2.0f;
\r
276 // TODO: speed this up
\r
277 X = (m[4] + m[1]) / scale;
\r
279 Z = (m[9] + m[6]) / scale;
\r
280 W = (m[8] - m[2]) / scale;
\r
284 // 3rd element of diag is greatest value
\r
285 // find scale according to 3rd element, and double it
\r
286 const f32 scale = sqrtf(1.0f + m[10] - m[0] - m[5]) * 2.0f;
\r
288 // TODO: speed this up
\r
289 X = (m[8] + m[2]) / scale;
\r
290 Y = (m[9] + m[6]) / scale;
\r
292 W = (m[1] - m[4]) / scale;
\r
296 return normalize();
\r
301 // multiplication operator
\r
302 inline quaternion quaternion::operator*(const quaternion& other) const
\r
306 tmp.W = (other.W * W) - (other.X * X) - (other.Y * Y) - (other.Z * Z);
\r
307 tmp.X = (other.W * X) + (other.X * W) + (other.Y * Z) - (other.Z * Y);
\r
308 tmp.Y = (other.W * Y) + (other.Y * W) + (other.Z * X) - (other.X * Z);
\r
309 tmp.Z = (other.W * Z) + (other.Z * W) + (other.X * Y) - (other.Y * X);
\r
315 // multiplication operator
\r
316 inline quaternion quaternion::operator*(f32 s) const
\r
318 return quaternion(s*X, s*Y, s*Z, s*W);
\r
322 // multiplication operator
\r
323 inline quaternion& quaternion::operator*=(f32 s)
\r
332 // multiplication operator
\r
333 inline quaternion& quaternion::operator*=(const quaternion& other)
\r
335 return (*this = other * (*this));
\r
339 inline quaternion quaternion::operator+(const quaternion& b) const
\r
341 return quaternion(X+b.X, Y+b.Y, Z+b.Z, W+b.W);
\r
344 #ifndef IRR_TEST_BROKEN_QUATERNION_USE
\r
345 // Creates a matrix from this quaternion
\r
346 inline matrix4 quaternion::getMatrix() const
\r
354 //! Faster method to create a rotation matrix, you should normalize the quaternion before!
\r
355 inline void quaternion::getMatrixFast( matrix4 &dest) const
\r
358 // gpu quaternion skinning => fast Bones transform chain O_O YEAH!
\r
359 // http://www.mrelusive.com/publications/papers/SIMD-From-Quaternion-to-Matrix-and-Back.pdf
\r
360 dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z;
\r
361 dest[1] = 2.0f*X*Y + 2.0f*Z*W;
\r
362 dest[2] = 2.0f*X*Z - 2.0f*Y*W;
\r
365 dest[4] = 2.0f*X*Y - 2.0f*Z*W;
\r
366 dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z;
\r
367 dest[6] = 2.0f*Z*Y + 2.0f*X*W;
\r
370 dest[8] = 2.0f*X*Z + 2.0f*Y*W;
\r
371 dest[9] = 2.0f*Z*Y - 2.0f*X*W;
\r
372 dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y;
\r
380 dest.setDefinitelyIdentityMatrix(false);
\r
384 Creates a matrix from this quaternion
\r
386 inline void quaternion::getMatrix(matrix4 &dest,
\r
387 const core::vector3df ¢er) const
\r
389 // ok creating a copy may be slower, but at least avoid internal
\r
390 // state chance (also because otherwise we cannot keep this method "const").
\r
392 quaternion q( *this);
\r
399 dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z;
\r
400 dest[1] = 2.0f*X*Y + 2.0f*Z*W;
\r
401 dest[2] = 2.0f*X*Z - 2.0f*Y*W;
\r
404 dest[4] = 2.0f*X*Y - 2.0f*Z*W;
\r
405 dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z;
\r
406 dest[6] = 2.0f*Z*Y + 2.0f*X*W;
\r
409 dest[8] = 2.0f*X*Z + 2.0f*Y*W;
\r
410 dest[9] = 2.0f*Z*Y - 2.0f*X*W;
\r
411 dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y;
\r
414 dest[12] = center.X;
\r
415 dest[13] = center.Y;
\r
416 dest[14] = center.Z;
\r
419 dest.setDefinitelyIdentityMatrix ( false );
\r
424 Creates a matrix from this quaternion
\r
425 Rotate about a center point
\r
427 core::quaternion q;
\r
428 q.rotationFromTo(vin[i].Normal, forward);
\r
429 q.getMatrix(lookat, center);
\r
432 m2.setInverseTranslation(center);
\r
435 inline void quaternion::getMatrixCenter(matrix4 &dest,
\r
436 const core::vector3df ¢er,
\r
437 const core::vector3df &translation) const
\r
439 quaternion q(*this);
\r
446 dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z;
\r
447 dest[1] = 2.0f*X*Y + 2.0f*Z*W;
\r
448 dest[2] = 2.0f*X*Z - 2.0f*Y*W;
\r
451 dest[4] = 2.0f*X*Y - 2.0f*Z*W;
\r
452 dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z;
\r
453 dest[6] = 2.0f*Z*Y + 2.0f*X*W;
\r
456 dest[8] = 2.0f*X*Z + 2.0f*Y*W;
\r
457 dest[9] = 2.0f*Z*Y - 2.0f*X*W;
\r
458 dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y;
\r
461 dest.setRotationCenter ( center, translation );
\r
464 // Creates a matrix from this quaternion
\r
465 inline void quaternion::getMatrix_transposed(matrix4 &dest) const
\r
467 quaternion q(*this);
\r
474 dest[0] = 1.0f - 2.0f*Y*Y - 2.0f*Z*Z;
\r
475 dest[4] = 2.0f*X*Y + 2.0f*Z*W;
\r
476 dest[8] = 2.0f*X*Z - 2.0f*Y*W;
\r
479 dest[1] = 2.0f*X*Y - 2.0f*Z*W;
\r
480 dest[5] = 1.0f - 2.0f*X*X - 2.0f*Z*Z;
\r
481 dest[9] = 2.0f*Z*Y + 2.0f*X*W;
\r
484 dest[2] = 2.0f*X*Z + 2.0f*Y*W;
\r
485 dest[6] = 2.0f*Z*Y - 2.0f*X*W;
\r
486 dest[10] = 1.0f - 2.0f*X*X - 2.0f*Y*Y;
\r
494 dest.setDefinitelyIdentityMatrix(false);
\r
498 // Inverts this quaternion
\r
499 inline quaternion& quaternion::makeInverse()
\r
501 X = -X; Y = -Y; Z = -Z;
\r
506 // sets new quaternion
\r
507 inline quaternion& quaternion::set(f32 x, f32 y, f32 z, f32 w)
\r
517 // sets new quaternion based on Euler angles
\r
518 inline quaternion& quaternion::set(f32 x, f32 y, f32 z)
\r
523 const f64 sr = sin(angle);
\r
524 const f64 cr = cos(angle);
\r
527 const f64 sp = sin(angle);
\r
528 const f64 cp = cos(angle);
\r
531 const f64 sy = sin(angle);
\r
532 const f64 cy = cos(angle);
\r
534 const f64 cpcy = cp * cy;
\r
535 const f64 spcy = sp * cy;
\r
536 const f64 cpsy = cp * sy;
\r
537 const f64 spsy = sp * sy;
\r
539 X = (f32)(sr * cpcy - cr * spsy);
\r
540 Y = (f32)(cr * spcy + sr * cpsy);
\r
541 Z = (f32)(cr * cpsy - sr * spcy);
\r
542 W = (f32)(cr * cpcy + sr * spsy);
\r
544 return normalize();
\r
547 // sets new quaternion based on Euler angles
\r
548 inline quaternion& quaternion::set(const core::vector3df& vec)
\r
550 return set( vec.X, vec.Y, vec.Z);
\r
553 // sets new quaternion based on other quaternion
\r
554 inline quaternion& quaternion::set(const core::quaternion& quat)
\r
556 return (*this=quat);
\r
560 //! returns if this quaternion equals the other one, taking floating point rounding errors into account
\r
561 inline bool quaternion::equals(const quaternion& other, const f32 tolerance) const
\r
563 return core::equals( X, other.X, tolerance) &&
\r
564 core::equals( Y, other.Y, tolerance) &&
\r
565 core::equals( Z, other.Z, tolerance) &&
\r
566 core::equals( W, other.W, tolerance);
\r
570 // normalizes the quaternion
\r
571 inline quaternion& quaternion::normalize()
\r
573 // removed conditional branch since it may slow down and anyway the condition was
\r
574 // false even after normalization in some cases.
\r
575 return (*this *= (f32)reciprocal_squareroot ( (f64)(X*X + Y*Y + Z*Z + W*W) ));
\r
578 // Set this quaternion to the result of the linear interpolation between two quaternions
\r
579 inline quaternion& quaternion::lerp( quaternion q1, quaternion q2, f32 time)
\r
581 const f32 scale = 1.0f - time;
\r
582 return (*this = (q1*scale) + (q2*time));
\r
585 // Set this quaternion to the result of the linear interpolation between two quaternions and normalize the result
\r
586 inline quaternion& quaternion::lerpN( quaternion q1, quaternion q2, f32 time)
\r
588 const f32 scale = 1.0f - time;
\r
589 return (*this = ((q1*scale) + (q2*time)).normalize() );
\r
592 // set this quaternion to the result of the interpolation between two quaternions
\r
593 inline quaternion& quaternion::slerp( quaternion q1, quaternion q2, f32 time, f32 threshold)
\r
595 f32 angle = q1.dotProduct(q2);
\r
597 // make sure we use the short rotation
\r
604 if (angle <= (1-threshold)) // spherical interpolation
\r
606 const f32 theta = acosf(angle);
\r
607 const f32 invsintheta = reciprocal(sinf(theta));
\r
608 const f32 scale = sinf(theta * (1.0f-time)) * invsintheta;
\r
609 const f32 invscale = sinf(theta * time) * invsintheta;
\r
610 return (*this = (q1*scale) + (q2*invscale));
\r
612 else // linear interpolation
\r
613 return lerpN(q1,q2,time);
\r
617 // calculates the dot product
\r
618 inline f32 quaternion::dotProduct(const quaternion& q2) const
\r
620 return (X * q2.X) + (Y * q2.Y) + (Z * q2.Z) + (W * q2.W);
\r
624 //! axis must be unit length, angle in radians
\r
625 inline quaternion& quaternion::fromAngleAxis(f32 angle, const vector3df& axis)
\r
627 const f32 fHalfAngle = 0.5f*angle;
\r
628 const f32 fSin = sinf(fHalfAngle);
\r
629 W = cosf(fHalfAngle);
\r
637 inline void quaternion::toAngleAxis(f32 &angle, core::vector3df &axis) const
\r
639 const f32 scale = sqrtf(X*X + Y*Y + Z*Z);
\r
641 if (core::iszero(scale) || W > 1.0f || W < -1.0f)
\r
650 const f32 invscale = reciprocal(scale);
\r
651 angle = 2.0f * acosf(W);
\r
652 axis.X = X * invscale;
\r
653 axis.Y = Y * invscale;
\r
654 axis.Z = Z * invscale;
\r
658 inline void quaternion::toEuler(vector3df& euler) const
\r
660 const f64 sqw = W*W;
\r
661 const f64 sqx = X*X;
\r
662 const f64 sqy = Y*Y;
\r
663 const f64 sqz = Z*Z;
\r
664 const f64 test = 2.0 * (Y*W - X*Z);
\r
666 if (core::equals(test, 1.0, 0.000001))
\r
668 // heading = rotation about z-axis
\r
669 euler.Z = (f32) (-2.0*atan2(X, W));
\r
670 // bank = rotation about x-axis
\r
672 // attitude = rotation about y-axis
\r
673 euler.Y = (f32) (core::PI64/2.0);
\r
675 else if (core::equals(test, -1.0, 0.000001))
\r
677 // heading = rotation about z-axis
\r
678 euler.Z = (f32) (2.0*atan2(X, W));
\r
679 // bank = rotation about x-axis
\r
681 // attitude = rotation about y-axis
\r
682 euler.Y = (f32) (core::PI64/-2.0);
\r
686 // heading = rotation about z-axis
\r
687 euler.Z = (f32) atan2(2.0 * (X*Y +Z*W),(sqx - sqy - sqz + sqw));
\r
688 // bank = rotation about x-axis
\r
689 euler.X = (f32) atan2(2.0 * (Y*Z +X*W),(-sqx - sqy + sqz + sqw));
\r
690 // attitude = rotation about y-axis
\r
691 euler.Y = (f32) asin( clamp(test, -1.0, 1.0) );
\r
696 inline vector3df quaternion::operator* (const vector3df& v) const
\r
698 // nVidia SDK implementation
\r
701 const vector3df qvec(X, Y, Z);
\r
702 uv = qvec.crossProduct(v);
\r
703 uuv = qvec.crossProduct(uv);
\r
707 return v + uv + uuv;
\r
710 // set quaternion to identity
\r
711 inline core::quaternion& quaternion::makeIdentity()
\r
720 inline core::quaternion& quaternion::rotationFromTo(const vector3df& from, const vector3df& to)
\r
722 // Based on Stan Melax's article in Game Programming Gems
\r
723 // Copy, since cannot modify local
\r
724 vector3df v0 = from;
\r
729 const f32 d = v0.dotProduct(v1);
\r
730 if (d >= 1.0f) // If dot == 1, vectors are the same
\r
732 return makeIdentity();
\r
734 else if (d <= -1.0f) // exactly opposite
\r
736 core::vector3df axis(1.0f, 0.f, 0.f);
\r
737 axis = axis.crossProduct(v0);
\r
738 if (axis.getLength()==0)
\r
740 axis.set(0.f,1.f,0.f);
\r
741 axis = axis.crossProduct(v0);
\r
743 // same as fromAngleAxis(core::PI, axis).normalize();
\r
744 return set(axis.X, axis.Y, axis.Z, 0).normalize();
\r
747 const f32 s = sqrtf( (1+d)*2 ); // optimize inv_sqrt
\r
748 const f32 invs = 1.f / s;
\r
749 const vector3df c = v0.crossProduct(v1)*invs;
\r
750 return set(c.X, c.Y, c.Z, s * 0.5f).normalize();
\r
754 } // end namespace core
\r
755 } // end namespace irr
\r