import {Prototype} from '../common/reflect/Prototype'; import {Euler} from './Euler'; import {Vector3} from './Vector3'; export class Quaternion { public x:number; public y:number; public z:number; public w:number; public constructor(x?: any, y?: any, z?: any, w?: any) { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = (w !== undefined) ? w : 1; } //============================================================ // 单位化处理。 // // @method //============================================================ public identity() { this.x = this.y = this.z = 0; this.w = 1; return this; } //============================================================ // 乘法处理。 // // @method // @param value:SQuaternion 四元数 //============================================================ public mul(value) { var x = this.x; var y = this.y; var z = this.z; var w = this.w; this.x = (w * value.x) + (x * value.w) + (y * value.z) - (z * value.y); this.y = (w * value.y) + (y * value.w) + (z * value.x) - (x * value.z); this.z = (w * value.z) + (z * value.w) + (x * value.y) - (y * value.x); this.w = (w * value.w) - (x * value.x) - (y * value.y) - (z * value.z); } //========================================================== // 用轴向量和旋转角创建一个四元组。 // // @method // @param axis:SVector3 方向轴 // @param angle:Number 弧度 //========================================================== public fromAxisAngle(axis, angle) { var r = angle * 0.5; var s = Math.sin(r); this.x = axis.x * s; this.y = axis.y * s; this.z = axis.z * s; this.w = Math.cos(r); } //========================================================== // 序列化数据到输出流里。 // // @param output 数据流 //========================================================== public serialize(output) { output.writeFloat(this.x); output.writeFloat(this.y); output.writeFloat(this.z); output.writeFloat(this.w); } //========================================================== // 从输入流里反序列化数据。 // // @param input 数据流 //========================================================== public unserialize(input) { this.x = input.readFloat(); this.y = input.readFloat(); this.z = input.readFloat(); this.w = input.readFloat(); } // public get x() { // return this._x; // } // public set x(value) { // this._x = value; // this.onChangeCallback(); // } // public get y() { // return this._y; // } // public set y(value) { // this._y = value; // this.onChangeCallback(); // } // public get z() { // return this._z; // } // public set z(value) { // this._z = value; // this.onChangeCallback(); // } // public get w() { // return this._w; // } // public set w(value) { // this._w = value; // this.onChangeCallback(); // } public set(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; this.onChangeCallback(); return this; } public clone() { return new (this as any).constructor(this.x, this.y, this.z, this.w); } public copy(quaternion) { this.x = quaternion.x; this.y = quaternion.y; this.z = quaternion.z; this.w = quaternion.w; this.onChangeCallback(); return this; } public setFromEuler(euler, update?: any) { if (euler instanceof Euler === false) { throw new Error('THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.'); } var c1 = Math.cos(euler._x / 2); var c2 = Math.cos(euler._y / 2); var c3 = Math.cos(euler._z / 2); var s1 = Math.sin(euler._x / 2); var s2 = Math.sin(euler._y / 2); var s3 = Math.sin(euler._z / 2); var order = euler.order; if (order === 'XYZ') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'YXZ') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (order === 'ZXY') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'ZYX') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (order === 'YZX') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'XZY') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } if (update !== false) this.onChangeCallback(); return this; } public setFromAxisAngle(axis, angle) { var halfAngle = angle / 2, s = Math.sin(halfAngle); this.x = axis.x * s; this.y = axis.y * s; this.z = axis.z * s; this.w = Math.cos(halfAngle); this.onChangeCallback(); return this; } public setFromRotationMatrix(m) { var te = m.elements, m11 = te[0], m12 = te[4], m13 = te[8], m21 = te[1], m22 = te[5], m23 = te[9], m31 = te[2], m32 = te[6], m33 = te[10], trace = m11 + m22 + m33, s; if (trace > 0) { s = 0.5 / Math.sqrt(trace + 1.0); this.w = 0.25 / s; this.x = (m32 - m23) * s; this.y = (m13 - m31) * s; this.z = (m21 - m12) * s; } else if (m11 > m22 && m11 > m33) { s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33); this.w = (m32 - m23) / s; this.x = 0.25 * s; this.y = (m12 + m21) / s; this.z = (m13 + m31) / s; } else if (m22 > m33) { s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33); this.w = (m13 - m31) / s; this.x = (m12 + m21) / s; this.y = 0.25 * s; this.z = (m23 + m32) / s; } else { s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22); this.w = (m21 - m12) / s; this.x = (m13 + m31) / s; this.y = (m23 + m32) / s; this.z = 0.25 * s; } this.onChangeCallback(); return this; } @Prototype() public setFromUnitVectors() { var v1, r; var EPS = 0.000001; return function(vFrom, vTo) { if (v1 === undefined) v1 = new Vector3(); r = vFrom.dot(vTo) + 1; if (r < EPS) { r = 0; if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) { v1.set(- vFrom.y, vFrom.x, 0); } else { v1.set(0, - vFrom.z, vFrom.y); } } else { v1.crossVectors(vFrom, vTo); } this._x = v1.x; this._y = v1.y; this._z = v1.z; this._w = r; this.normalize(); return this; }; } public inverse() { this.conjugate().normalize(); return this; } public conjugate() { this.x *= - 1; this.y *= - 1; this.z *= - 1; this.onChangeCallback(); return this; } public dot(v) { return this.x * v._x + this.y * v._y + this.z * v._z + this.w * v._w; } public lengthSq() { return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; } public length() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); } public normalize() { var l = this.length(); if (l === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 1; } else { l = 1 / l; this.x = this.x * l; this.y = this.y * l; this.z = this.z * l; this.w = this.w * l; } this.onChangeCallback(); return this; } public multiply(q, p) { if (p !== undefined) { console.warn('THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.'); return this.multiplyQuaternions(q, p); } return this.multiplyQuaternions(this, q); } public multiplyQuaternions(a, b) { var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; this.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; this.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; this.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; this.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; this.onChangeCallback(); return this; } public slerp(qb, t) { if (t === 0) return this; if (t === 1) return this.copy(qb); var x = this.x, y = this.y, z = this.z, w = this.w; var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; if (cosHalfTheta < 0) { this.w = - qb._w; this.x = - qb._x; this.y = - qb._y; this.z = - qb._z; cosHalfTheta = - cosHalfTheta; } else { this.copy(qb); } if (cosHalfTheta >= 1.0) { this.w = w; this.x = x; this.y = y; this.z = z; return this; } var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); if (Math.abs(sinHalfTheta) < 0.001) { this.w = 0.5 * (w + this.w); this.x = 0.5 * (x + this.x); this.y = 0.5 * (y + this.y); this.z = 0.5 * (z + this.z); return this; } var halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta); var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta, ratioB = Math.sin(t * halfTheta) / sinHalfTheta; this.w = (w * ratioA + this.w * ratioB); this.x = (x * ratioA + this.x * ratioB); this.y = (y * ratioA + this.y * ratioB); this.z = (z * ratioA + this.z * ratioB); this.onChangeCallback(); return this; } public equals(quaternion) { return (quaternion._x === this.x) && (quaternion._y === this.y) && (quaternion._z === this.z) && (quaternion._w === this.w); } public fromArray(array, offset) { if (offset === undefined) offset = 0; this.x = array[offset]; this.y = array[offset + 1]; this.z = array[offset + 2]; this.w = array[offset + 3]; this.onChangeCallback(); return this; } public toArray(array, offset) { if (array === undefined) array = []; if (offset === undefined) offset = 0; array[offset] = this.x; array[offset + 1] = this.y; array[offset + 2] = this.z; array[offset + 3] = this.w; return array; } public onChange(callback) { this.onChangeCallback = callback; return this; } public onChangeCallback() { } // Object.assign(THREE.Quaternion, { // slerp: function(qa, qb, qm, t) { // return qm.copy(qa).slerp(qb, t); // }, // slerpFlat: function( // dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t) { // // fuzz-free, array-based Quaternion SLERP operation // var x0 = src0[srcOffset0 + 0], // y0 = src0[srcOffset0 + 1], // z0 = src0[srcOffset0 + 2], // w0 = src0[srcOffset0 + 3], // x1 = src1[srcOffset1 + 0], // y1 = src1[srcOffset1 + 1], // z1 = src1[srcOffset1 + 2], // w1 = src1[srcOffset1 + 3]; // if (w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1) { // var s = 1 - t, // cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, // dir = (cos >= 0 ? 1 : - 1), // sqrSin = 1 - cos * cos; // // Skip the Slerp for tiny steps to avoid numeric problems: // if (sqrSin > Number.EPSILON) { // var sin = Math.sqrt(sqrSin), // len = Math.atan2(sin, cos * dir); // s = Math.sin(s * len) / sin; // t = Math.sin(t * len) / sin; // } // var tDir = t * dir; // x0 = x0 * s + x1 * tDir; // y0 = y0 * s + y1 * tDir; // z0 = z0 * s + z1 * tDir; // w0 = w0 * s + w1 * tDir; // // Normalize in case we just did a lerp: // if (s === 1 - t) { // var f = 1 / Math.sqrt(x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0); // x0 *= f; // y0 *= f; // z0 *= f; // w0 *= f; // } // } // dst[dstOffset] = x0; // dst[dstOffset + 1] = y0; // dst[dstOffset + 2] = z0; // dst[dstOffset + 3] = w0; // } // }); }