package kha.math; import kha.math.Vector3; import kha.math.Matrix4; @:structInit class Quaternion { var values: Array; public inline function new(x: Float = 0, y: Float = 0, z: Float = 0, w: Float = 1): Void { values = new Array(); values.push(x); values.push(y); values.push(z); values.push(w); } // Axis has to be normalized public inline static function fromAxisAngle(axis: Vector3, radians: Float): Quaternion { var q: Quaternion = new Quaternion(); q.w = Math.cos(radians / 2.0); q.x = q.y = q.z = Math.sin(radians / 2.0); q.x *= axis.x; q.y *= axis.y; q.z *= axis.z; return q; } public function slerp(t: Float, q: Quaternion) { var epsilon: Float = 0.0005; var dot = dot(q); if (dot > 1 - epsilon) { var result: Quaternion = q.add((this.sub(q)).scaled(t)); result.normalize(); return result; } if (dot < 0) dot = 0; if (dot > 1) dot = 1; var theta0: Float = Math.acos(dot); var theta: Float = theta0 * t; var q2: Quaternion = q.sub(scaled(dot)); q2.normalize(); var result: Quaternion = scaled(Math.cos(theta)).add(q2.scaled(Math.sin(theta))); result.normalize(); return result; } // TODO: This should be multiplication public inline function rotated(b: Quaternion): Quaternion { var q: Quaternion = new Quaternion(); q.w = w * b.w - x * b.x - y * b.y - z * b.z; q.x = w * b.x + x * b.w + y * b.z - z * b.y; q.y = w * b.y + y * b.w + z * b.x - x * b.z; q.z = w * b.z + z * b.w + x * b.y - y * b.x; q.normalize(); return q; } public inline function scaled(scale: Float): Quaternion { return new Quaternion(x * scale, y * scale, z * scale, w * scale); } public inline function scale(scale: Float) { x = x * scale; y = y * scale; z = z * scale; w = w * scale; } public inline function matrix(): Matrix4 { var s: Float = 2.0; var xs: Float = x * s; var ys: Float = y * s; var zs: Float = z * s; var wx: Float = w * xs; var wy: Float = w * ys; var wz: Float = w * zs; var xx: Float = x * xs; var xy: Float = x * ys; var xz: Float = x * zs; var yy: Float = y * ys; var yz: Float = y * zs; var zz: Float = z * zs; return new Matrix4(1 - (yy + zz), xy - wz, xz + wy, 0, xy + wz, 1 - (xx + zz), yz - wx, 0, xz - wy, yz + wx, 1 - (xx + yy), 0, 0, 0, 0, 1); } public inline function get(index: Int): Float { return values[index]; } public inline function set(index: Int, value: Float): Void { values[index] = value; } public var x(get, set): Float; public var y(get, set): Float; public var z(get, set): Float; public var w(get, set): Float; public var length(get, set): Float; function get_x(): Float { return values[0]; } function set_x(value: Float): Float { return values[0] = value; } function get_y(): Float { return values[1]; } function set_y(value: Float): Float { return values[1] = value; } function get_z(): Float { return values[2]; } function set_z(value: Float): Float { return values[2] = value; } function get_w(): Float { return values[3]; } function set_w(value: Float): Float { return values[3] = value; } // TODO: Isn't this code wrong? Is wrong in Vector4 for sure! (Missing w in the length) function get_length(): Float { return Math.sqrt(x * x + y * y + z * z + w * w); } function set_length(length: Float): Float { if (get_length() == 0) return 0; var mul = length / get_length(); x *= mul; y *= mul; z *= mul; return length; } // For adding a (scaled) axis-angle representation of a quaternion public inline function addVector(vec: Vector3): Quaternion { var result: Quaternion = new Quaternion(x, y, z, w); var q1: Quaternion = new Quaternion(0, vec.x, vec.y, vec.z); q1 = q1.mult(result); result.x += q1.x * 0.5; result.y += q1.y * 0.5; result.z += q1.z * 0.5; result.w += q1.w * 0.5; return result; } public inline function add(q: Quaternion): Quaternion { return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); } public inline function sub(q: Quaternion): Quaternion { return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); } // TODO: Check again, but I think the code in Kore is wrong public inline function mult(r: Quaternion): Quaternion { var q: Quaternion = new Quaternion(); q.x = w * r.x + x * r.w + y * r.z - z * r.y; q.y = w * r.y - x * r.z + y * r.w + z * r.x; q.z = w * r.z + x * r.y - y * r.x + z * r.w; q.w = w * r.w - x * r.x - y * r.y - z * r.z; return q; } public inline function normalize() { scale(1.0 / length); } public inline function dot(q: Quaternion) { return x * q.x + y * q.y + z * q.z + w * q.w; } // GetEulerAngles extracts Euler angles from the quaternion, in the specified order of // axis rotations and the specified coordinate system. Right-handed coordinate system // is the default, with CCW rotations while looking in the negative axis direction. // Here a,b,c, are the Yaw/Pitch/Roll angles to be returned. // rotation a around axis A1 // is followed by rotation b around axis A2 // is followed by rotation c around axis A3 // rotations are CCW or CW (D) in LH or RH coordinate system (S) public static inline var AXIS_X: Int = 0; public static inline var AXIS_Y: Int = 1; public static inline var AXIS_Z: Int = 2; public function getEulerAngles(A1: Int, A2: Int, A3: Int, S: Int = 1, D: Int = 1): Vector3 { var result: Vector3 = new Vector3(); var Q: Array = new Array(); Q[0] = x; Q[1] = y; Q[2] = z; var ww: Float = w * w; var Q11: Float = Q[A1] * Q[A1]; var Q22: Float = Q[A2] * Q[A2]; var Q33: Float = Q[A3] * Q[A3]; var psign: Float = -1; var SingularityRadius: Float = 0.0000001; var PiOver2: Float = Math.PI / 2.0; // Determine whether even permutation if (((A1 + 1) % 3 == A2) && ((A2 + 1) % 3 == A3)) { psign = 1; } var s2: Float = psign * 2.0 * (psign * w * Q[A2] + Q[A1] * Q[A3]); if (s2 < -1 + SingularityRadius) { // South pole singularity result.x = 0; result.y = -S * D * PiOver2; result.z = S * D * Math.atan2(2 * (psign * Q[A1] * Q[A2] + w * Q[A3]), ww + Q22 - Q11 - Q33); } else if (s2 > 1 - SingularityRadius) { // North pole singularity result.x = 0; result.y = S * D * PiOver2; result.z = S * D * Math.atan2(2 * (psign * Q[A1] * Q[A2] + w * Q[A3]), ww + Q22 - Q11 - Q33); } else { result.x = -S * D * Math.atan2(-2 * (w * Q[A1] - psign * Q[A2] * Q[A3]), ww + Q33 - Q11 - Q22); result.y = S * D * Math.asin(s2); result.z = S * D * Math.atan2(2 * (w * Q[A3] - psign * Q[A1] * Q[A2]), ww + Q11 - Q22 - Q33); } return result; } }