// Copyright 2013,2016 <>< C. N. Lohr.  This file licensed under the terms of the MIT license.
#if defined(_WIN32) && !defined(TCC)
#define LINMATH_EXPORT __declspec(dllexport)
#endif
#include "linmath.h"
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>

#include <cnmatrix/cn_matrix.h>
#if !defined(__FreeBSD__) && !defined(__APPLE__)
#include <assert.h>
#include <malloc.h>
#endif

#ifndef M_PI
# define M_PI           3.14159265358979323846  /* pi */
#endif

inline void cross3d(FLT *out, const FLT *a, const FLT *b) {
	out[0] = a[1] * b[2] - a[2] * b[1];
	out[1] = a[2] * b[0] - a[0] * b[2];
	out[2] = a[0] * b[1] - a[1] * b[0];
}

inline void sub3d(FLT *out, const FLT *a, const FLT *b) {
	out[0] = a[0] - b[0];
	out[1] = a[1] - b[1];
	out[2] = a[2] - b[2];
}
// out = a + b * s
LINMATH_EXPORT void addscalednd(FLT *out, const FLT *a, const FLT *b, FLT s, size_t size) {
	for (int i = 0; i < size; i++)
		out[i] = a[i] + s * b[i];
}

LINMATH_EXPORT void addnd(FLT *out, const FLT *a, const FLT *b, size_t size) {
	for (int i = 0; i < size; i++)
		out[i] = a[i] + b[i];
}
LINMATH_EXPORT void subnd(FLT *out, const FLT *a, const FLT *b, size_t size) {
	for (int i = 0; i < size; i++)
		out[i] = a[i] - b[i];
}
LINMATH_EXPORT void mulnd(FLT *out, const FLT *a, const FLT *b, size_t size) {
	for (int i = 0; i < size; i++)
		out[i] = a[i] * b[i];
}
inline void add3d(FLT *out, const FLT *a, const FLT *b) {
	out[0] = a[0] + b[0];
	out[1] = a[1] + b[1];
	out[2] = a[2] + b[2];
}

inline void invert3d(FLT *out, const FLT *a) {
	out[0] = 1. / a[0];
	out[1] = 1. / a[1];
	out[2] = 1. / a[2];
}
inline void scalend(FLT *out, const FLT *a, FLT scalar, size_t size) {
	for (size_t i = 0; i < size; i++) {
		out[i] = a[i] * scalar;
	}
}
inline void scale3d(FLT *out, const FLT *a, FLT scalar) {
	out[0] = a[0] * scalar;
	out[1] = a[1] * scalar;
	out[2] = a[2] * scalar;
}

LINMATH_EXPORT FLT norm3d(const FLT *in) { return FLT_SQRT(in[0] * in[0] + in[1] * in[1] + in[2] * in[2]); }
LINMATH_EXPORT FLT normnd2(const FLT *in, size_t n) {
	FLT r = 0;
	for (int i = 0; i < n; i++) {
		r += in[i] * in[i];
	}
	return r;
}
LINMATH_EXPORT FLT normnd(const FLT *in, size_t n) { return FLT_SQRT(normnd2(in, n)); }
inline void normalize3d(FLT *out, const FLT *in) {
	FLT r = ((FLT)1.) / norm3d(in);
	out[0] = in[0] * r;
	out[1] = in[1] * r;
	out[2] = in[2] * r;
}

void linmath_interpolate(FLT *out, int n, const FLT *A, const FLT *B, FLT t) {
	for (int i = 0; i < n; i++) {
		out[i] = A[i] + (B[i] - A[i]) * t;
	}
}

LINMATH_EXPORT FLT dot3d(const FLT *a, const FLT *b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
LINMATH_EXPORT FLT dotnd(const FLT *a, const FLT *b, size_t n) {
	FLT rtn = 0;
	for (int i = 0; i < n; i++) {
		rtn += a[i] * b[i];
	}
	return rtn;
}
LINMATH_EXPORT FLT dotnd_strided(const FLT *a, const FLT *b, size_t n, int a_stride, int b_stride) {
	FLT rtn = 0;
	for (int i = 0; i < n; i++) {
		rtn += a[i * a_stride] * b[i * b_stride];
	}
	return rtn;
}
int compare3d(const FLT *a, const FLT *b, FLT epsilon) {
	if (!a || !b)
		return 0;
	if (a[2] - b[2] > epsilon)
		return 1;
	if (b[2] - a[2] > epsilon)
		return -1;
	if (a[1] - b[1] > epsilon)
		return 1;
	if (b[1] - a[1] > epsilon)
		return -1;
	if (a[0] - b[0] > epsilon)
		return 1;
	if (b[0] - a[0] > epsilon)
		return -1;
	return 0;
}

inline void copy3d(FLT *out, const FLT *in) {
	out[0] = in[0];
	out[1] = in[1];
	out[2] = in[2];
}
LINMATH_EXPORT void copynd(FLT *out, const FLT *in, size_t n) { memcpy(out, in, n * sizeof(FLT)); }
LINMATH_EXPORT FLT magnitude3d(const FLT *a) { return FLT_SQRT(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); }
FLT dist3d(const FLT *a, const FLT *b) {
	LinmathPoint3d tmp;
	sub3d(tmp, a, b);
	return magnitude3d(tmp);
}
LINMATH_EXPORT FLT distnd(const FLT *a, const FLT *b, size_t len) {
	FLT s = 0;
	for (int i = 0; i < len; i++) {
		FLT d = a[i] - b[i];
		s += d * d;
	}
	return FLT_SQRT(s);
}
FLT anglebetween3d(FLT *a, FLT *b) {
	FLT an[3];
	FLT bn[3];
	normalize3d(an, a);
	normalize3d(bn, b);
	FLT dot = dot3d(an, bn);
	if (dot < -0.9999999)
		return LINMATHPI;
	if (dot > 0.9999999)
		return 0;
	return FLT_ACOS(dot);
}

LINMATH_EXPORT void center3d(FLT *out_pts, FLT *out_mean, const FLT *pts, int num_pts) {
	FLT center[3];
	if (out_mean == 0)
		out_mean = center;
	mean3d(out_mean, pts, num_pts);

	for (int i = 0; i < num_pts; i++) {
		for (int j = 0; j < 3; j++) {
			out_pts[i * 3 + j] = pts[i * 3 + j] - out_mean[j];
		}
	}
}

LINMATH_EXPORT void mean3d(LinmathVec3d out, const FLT *pts, int num_pts) {
	for (int i = 0; i < 3; i++) {
		out[i] = 0;
	}

	for (int i = 0; i < num_pts; i++) {
		for (int j = 0; j < 3; j++) {
			out[j] += pts[i * 3 + j];
		}
	}

	for (int j = 0; j < 3; j++) {
		out[j] = out[j] / (FLT)num_pts;
	}
}

// algorithm found here: http://inside.mines.edu/fs_home/gmurray/ArbitraryAxisRotation/
inline void rotatearoundaxis(FLT *outvec3, const FLT *invec3, const FLT *_axis, FLT angle) {
	FLT axis[3];
	normalize3d(axis, _axis);

	FLT s = FLT_SIN(angle);
	FLT c = FLT_COS(angle);

	FLT u = axis[0];
	FLT v = axis[1];
	FLT w = axis[2];

	FLT x = invec3[0];
	FLT y = invec3[1];
	FLT z = invec3[2];

	outvec3[0] = u * (u * x + v * y + w * z) * (1 - c) + x * c + (-w * y + v * z) * s;
	outvec3[1] = v * (u * x + v * y + w * z) * (1 - c) + y * c + (w * x - u * z) * s;
	outvec3[2] = w * (u * x + v * y + w * z) * (1 - c) + z * c + (-v * x + u * y) * s;
}

inline void axisanglerotatevector(FLT *vec3out, const LinmathAxisAngle axisAngle, const FLT *vec3in) {
	FLT angle = norm3d(axisAngle);
	if (angle == 0.0) {
		copy3d(vec3out, vec3in);
	} else {
		rotatearoundaxis(vec3out, vec3in, axisAngle, angle);
	}
}

inline void angleaxisfrom2vect(FLT *angle, FLT *axis, FLT *src, FLT *dest) {
	FLT v0[3];
	FLT v1[3];
	normalize3d(v0, src);
	normalize3d(v1, dest);

	FLT d = dot3d(v0, v1); // v0.dotProduct(v1);

	// If dot == 1, vectors are the same
	// If dot == -1, vectors are opposite
	if (FLT_FABS(d - 1) < DEFAULT_EPSILON) {
		axis[0] = 0;
		axis[1] = 1;
		axis[2] = 0;
		*angle = 0;
		return;
	} else if (FLT_FABS(d + 1) < DEFAULT_EPSILON) {
		axis[0] = 0;
		axis[1] = 1;
		axis[2] = 0;
		*angle = LINMATHPI;
		return;
	}

	FLT v0Len = magnitude3d(v0);
	FLT v1Len = magnitude3d(v1);

	*angle = FLT_ACOS(d / (v0Len * v1Len));

	// cross3d(c, v0, v1);
	cross3d(axis, v1, v0);
}

inline void axisanglefromquat(FLT *angle, FLT *axis, const LinmathQuat q) {
	// this way might be fine, too.
	// FLT dist = FLT_SQRT((q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]));
	//
	//*angle = 2 * FLT_ATAN2(dist, q[0]);

	// axis[0] = q[1] / dist;
	// axis[1] = q[2] / dist;
	// axis[2] = q[3] / dist;

	// Good mathematical foundation for this algorithm found here:
	// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm

	FLT tmp[4] = {q[0], q[1], q[2], q[3]};

	quatnormalize(tmp, q);

	if (FLT_FABS(q[0] - 1) < FLT_EPSILON) {
		// we have a degenerate case where we're rotating approx. 0 degrees
		*angle = 0;
		axis[0] = 1;
		axis[1] = 0;
		axis[2] = 0;
		return;
	}

	axis[0] = tmp[1] / sqrt(1 - (tmp[0] * tmp[0]));
	axis[1] = tmp[2] / sqrt(1 - (tmp[0] * tmp[0]));
	axis[2] = tmp[3] / sqrt(1 - (tmp[0] * tmp[0]));

	*angle = 2 * FLT_ACOS(tmp[0]);
}

/////////////////////////////////////QUATERNIONS//////////////////////////////////////////
// Originally from Mercury (Copyright (C) 2009 by Joshua Allen, Charles Lohr, Adam Lowman)
// Under the mit/X11 license.

inline FLT quatdifference(const LinmathQuat q1, const LinmathQuat q2) {
	LinmathQuat diff;
	quatfind(diff, q1, q2);
	return 1. - fabs(diff[0]);
}
FLT quatdist(const LinmathQuat q1, const LinmathQuat q2) {
	FLT rtn = 0;
	for (int i = 0; i < 4; i++) {
		rtn += q1[i] * q2[i];
	}
	rtn = linmath_max(1., linmath_min(-1, rtn));
	return 2 * acos(FLT_FABS(rtn));
}

inline bool quatiszero(const LinmathQuat q) { return q[0] == 0 && q[1] == 0 && q[2] == 0 && q[3] == 0; }
inline void quatset(LinmathQuat q, FLT w, FLT x, FLT y, FLT z) {
	q[0] = w;
	q[1] = x;
	q[2] = y;
	q[3] = z;
}

inline void quatsetnone(LinmathQuat q) {
	q[0] = 1;
	q[1] = 0;
	q[2] = 0;
	q[3] = 0;
}

inline void quatcopy(LinmathQuat qout, const LinmathQuat qin) {
	qout[0] = qin[0];
	qout[1] = qin[1];
	qout[2] = qin[2];
	qout[3] = qin[3];
}

inline void quatfromeuler(LinmathQuat q, const LinmathEulerAngle euler) {
	FLT X = euler[0] / 2.0f; // roll
	FLT Y = euler[1] / 2.0f; // pitch
	FLT Z = euler[2] / 2.0f; // yaw

	FLT cx = FLT_COS(X);
	FLT sx = FLT_SIN(X);
	FLT cy = FLT_COS(Y);
	FLT sy = FLT_SIN(Y);
	FLT cz = FLT_COS(Z);
	FLT sz = FLT_SIN(Z);

	// Correct according to
	// http://en.wikipedia.org/wiki/Conversion_between_MQuaternions_and_Euler_angles
	q[0] = cx * cy * cz + sx * sy * sz; // q1
	q[1] = sx * cy * cz - cx * sy * sz; // q2
	q[2] = cx * sy * cz + sx * cy * sz; // q3
	q[3] = cx * cy * sz - sx * sy * cz; // q4
	quatnormalize(q, q);
}

inline void quattoeuler(LinmathEulerAngle euler, const LinmathQuat q) {
	// According to http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles (Oct 26, 2009)
	euler[0] = FLT_ATAN2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2]));
	euler[1] = FLT_ASIN(2 * (q[0] * q[2] - q[3] * q[1]));
	euler[2] = FLT_ATAN2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3]));
}

inline void quatfromaxisanglemag(LinmathQuat q, const LinmathAxisAngle axisangle) {
	FLT radians = norm3d(axisangle);

	if (radians == 0.0) {
		quatcopy(q, LinmathQuat_Identity);
	} else {
		FLT sn = FLT_SIN(radians / 2.0f);
		q[0] = FLT_COS(radians / 2.0f);
		q[1] = sn * axisangle[0] / radians;
		q[2] = sn * axisangle[1] / radians;
		q[3] = sn * axisangle[2] / radians;
		quatnormalize(q, q);
	}
}
inline void quatfromaxisangle(LinmathQuat q, const FLT *axis, FLT radians) {
	if (radians == 0.0) {
		quatcopy(q, LinmathQuat_Identity);
		return;
	}
	FLT v[3];
	normalize3d(v, axis);

	FLT sn = FLT_SIN(radians / 2.0f);
	q[0] = FLT_COS(radians / 2.0f);
	q[1] = sn * v[0];
	q[2] = sn * v[1];
	q[3] = sn * v[2];

	quatnormalize(q, q);
}

inline FLT quatmagnitude(const LinmathQuat q) {
	return FLT_SQRT((q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]));
}

inline FLT quatinvsqmagnitude(const LinmathQuat q) {
	return ((FLT)1.) / FLT_SQRT((q[0] * q[0]) + (q[1] * q[1]) + (q[2] * q[2]) + (q[3] * q[3]));
}

inline void quatnormalize(LinmathQuat qout, const LinmathQuat qin) {
	FLT norm = quatmagnitude(qin);
	quatdivs(qout, qin, norm);
}

inline void quattomatrix(FLT *matrix44, const LinmathQuat qin) {
	FLT q[4];
	quatnormalize(q, qin);

	// Reduced calculation for speed
	FLT xx = 2 * q[1] * q[1];
	FLT xy = 2 * q[1] * q[2];
	FLT xz = 2 * q[1] * q[3];
	FLT xw = 2 * q[1] * q[0];

	FLT yy = 2 * q[2] * q[2];
	FLT yz = 2 * q[2] * q[3];
	FLT yw = 2 * q[2] * q[0];

	FLT zz = 2 * q[3] * q[3];
	FLT zw = 2 * q[3] * q[0];

	// opengl major
	matrix44[0] = 1 - yy - zz;
	matrix44[1] = xy - zw;
	matrix44[2] = xz + yw;
	matrix44[3] = 0;

	matrix44[4] = xy + zw;
	matrix44[5] = 1 - xx - zz;
	matrix44[6] = yz - xw;
	matrix44[7] = 0;

	matrix44[8] = xz - yw;
	matrix44[9] = yz + xw;
	matrix44[10] = 1 - xx - yy;
	matrix44[11] = 0;

	matrix44[12] = 0;
	matrix44[13] = 0;
	matrix44[14] = 0;
	matrix44[15] = 1;
}
LINMATH_EXPORT void quatfromcnMatrix(LinmathQuat q, const struct CnMat *m) {
	FLT m00 = cnMatrixGet(m, 0, 0), m01 = cnMatrixGet(m, 0, 1), m02 = cnMatrixGet(m, 0, 2), m10 = cnMatrixGet(m, 1, 0),
		m11 = cnMatrixGet(m, 1, 1), m12 = cnMatrixGet(m, 1, 2), m20 = cnMatrixGet(m, 2, 0), m21 = cnMatrixGet(m, 2, 1),
		m22 = cnMatrixGet(m, 2, 2);

	FLT tr = m00 + m11 + m22;

	if (tr > 0) {
		FLT S = sqrt(tr + 1.0) * 2; // S=4*qw
		q[0] = 0.25 * S;
		q[1] = (m21 - m12) / S;
		q[2] = (m02 - m20) / S;
		q[3] = (m10 - m01) / S;
	} else if ((m00 > m11) & (m00 > m22)) {
		FLT S = sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*q[1]
		q[0] = (m21 - m12) / S;
		q[1] = 0.25 * S;
		q[2] = (m01 + m10) / S;
		q[3] = (m02 + m20) / S;
	} else if (m11 > m22) {
		FLT S = sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*q[2]
		q[0] = (m02 - m20) / S;
		q[1] = (m01 + m10) / S;
		q[2] = 0.25 * S;
		q[3] = (m12 + m21) / S;
	} else {
		FLT S = sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*q[3]
		q[0] = (m10 - m01) / S;
		q[1] = (m02 + m20) / S;
		q[2] = (m12 + m21) / S;
		q[3] = 0.25 * S;
	}
}
inline void quatfrommatrix33(LinmathQuat q, const FLT *m) {
	FLT m00 = m[0], m01 = m[1], m02 = m[2], m10 = m[3], m11 = m[4], m12 = m[5], m20 = m[6], m21 = m[7], m22 = m[8];

	FLT tr = m00 + m11 + m22;

	if (tr > 0) {
		FLT S = sqrt(tr + 1.0) * 2; // S=4*qw
		q[0] = 0.25 * S;
		q[1] = (m21 - m12) / S;
		q[2] = (m02 - m20) / S;
		q[3] = (m10 - m01) / S;
	} else if ((m00 > m11) & (m00 > m22)) {
		FLT S = sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*q[1]
		q[0] = (m21 - m12) / S;
		q[1] = 0.25 * S;
		q[2] = (m01 + m10) / S;
		q[3] = (m02 + m20) / S;
	} else if (m11 > m22) {
		FLT S = sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*q[2]
		q[0] = (m02 - m20) / S;
		q[1] = (m01 + m10) / S;
		q[2] = 0.25 * S;
		q[3] = (m12 + m21) / S;
	} else {
		FLT S = sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*q[3]
		q[0] = (m10 - m01) / S;
		q[1] = (m02 + m20) / S;
		q[2] = (m12 + m21) / S;
		q[3] = 0.25 * S;
	}
}

inline void quatfrommatrix(LinmathQuat q, const FLT *matrix44) {
	// Algorithm from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
	FLT tr = matrix44[0] + matrix44[5] + matrix44[10];

	if (tr > 0) {
		FLT S = FLT_SQRT(tr + 1.0) * 2.; // S=4*qw
		q[0] = 0.25f * S;
		q[1] = (matrix44[9] - matrix44[6]) / S;
		q[2] = (matrix44[2] - matrix44[8]) / S;
		q[3] = (matrix44[4] - matrix44[1]) / S;
	} else if ((matrix44[0] > matrix44[5]) && (matrix44[0] > matrix44[10])) {
		FLT S = FLT_SQRT(1.0 + matrix44[0] - matrix44[5] - matrix44[10]) * 2.; // S=4*qx
		q[0] = (matrix44[9] - matrix44[6]) / S;
		q[1] = 0.25f * S;
		q[2] = (matrix44[1] + matrix44[4]) / S;
		q[3] = (matrix44[2] + matrix44[8]) / S;
	} else if (matrix44[5] > matrix44[10]) {
		FLT S = FLT_SQRT(1.0 + matrix44[5] - matrix44[0] - matrix44[10]) * 2.; // S=4*qy
		q[0] = (matrix44[2] - matrix44[8]) / S;
		q[1] = (matrix44[1] + matrix44[4]) / S;
		q[2] = 0.25f * S;
		q[3] = (matrix44[6] + matrix44[9]) / S;
	} else {
		FLT S = FLT_SQRT(1.0 + matrix44[10] - matrix44[0] - matrix44[5]) * 2.; // S=4*qz
		q[0] = (matrix44[4] - matrix44[1]) / S;
		q[1] = (matrix44[2] + matrix44[8]) / S;
		q[2] = (matrix44[6] + matrix44[9]) / S;
		q[3] = 0.25 * S;
	}
}

// Algorithm from http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/
inline void quattomatrix33(FLT *matrix33, const LinmathQuat qin) {
	FLT q[4];
	quatnormalize(q, qin);

	// Reduced calulation for speed
	FLT xx = 2 * q[1] * q[1];
	FLT xy = 2 * q[1] * q[2];
	FLT xz = 2 * q[1] * q[3];
	FLT xw = 2 * q[1] * q[0];

	FLT yy = 2 * q[2] * q[2];
	FLT yz = 2 * q[2] * q[3];
	FLT yw = 2 * q[2] * q[0];

	FLT zz = 2 * q[3] * q[3];
	FLT zw = 2 * q[3] * q[0];

	// opengl major
	matrix33[0] = 1 - yy - zz;
	matrix33[1] = xy + zw;
	matrix33[2] = xz - yw;

	matrix33[3] = xy - zw;
	matrix33[4] = 1 - xx - zz;
	matrix33[5] = yz + xw;

	matrix33[6] = xz + yw;
	matrix33[7] = yz - xw;
	matrix33[8] = 1 - xx - yy;
}

inline void quatgetconjugate(LinmathQuat qout, const LinmathQuat qin) {
	qout[0] = qin[0];
	qout[1] = -qin[1];
	qout[2] = -qin[2];
	qout[3] = -qin[3];
}

inline void quatgetreciprocal(LinmathQuat qout, const LinmathQuat qin) {
	FLT m = quatinvsqmagnitude(qin);
	quatgetconjugate(qout, qin);
	quatscale(qout, qout, m);
}

inline void quatconjugateby(LinmathQuat q, const LinmathQuat r, const LinmathQuat v) {
	LinmathQuat ir;
	quatgetconjugate(ir, r);
	quatrotateabout(q, ir, v);
	quatrotateabout(q, q, r);
	quatnormalize(q, q);
}
inline void quatsub(LinmathQuat qout, const LinmathQuat a, const LinmathQuat b) {
	qout[0] = a[0] - b[0];
	qout[1] = a[1] - b[1];
	qout[2] = a[2] - b[2];
	qout[3] = a[3] - b[3];
}

inline void quatadd(LinmathQuat qout, const LinmathQuat a, const LinmathQuat b) {
	qout[0] = a[0] + b[0];
	qout[1] = a[1] + b[1];
	qout[2] = a[2] + b[2];
	qout[3] = a[3] + b[3];
}

void quatfind_between_vectors(LinmathQuat q, const LinmathPoint3d _p0, const LinmathPoint3d _p1) {
	LinmathPoint3d p0, p1;
	normalize3d(p0, _p0);
	normalize3d(p1, _p1);

	FLT d = dot3d(p0, p1);

	if (d < -0.999999) {
		LinmathPoint3d tmp, xUnit = {1, 0, 0}, yUnit = {0, 1, 0};

		cross3d(tmp, xUnit, p0);
		if (norm3d(tmp) < .00001)
			cross3d(tmp, yUnit, p0);
		normalize3d(tmp, tmp);
		scale3d(tmp, tmp, LINMATHPI);

		quatfromaxisanglemag(q, tmp);
	} else if (d > 1.) {
		quatcopy(q, LinmathQuat_Identity);
	} else {
		cross3d(q + 1, p0, p1);
		q[0] = 1 + d;
		quatnormalize(q, q);
	}
}
inline void quatfind(LinmathQuat q, const LinmathQuat q0, const LinmathQuat q1) {
	LinmathQuat iq0;
	quatgetconjugate(iq0, q0);
	quatrotateabout(q, q1, iq0);
	if (q[0] == -1)
		q[0] = 1;
}
inline void axisanglerotateabout(LinmathAxisAngle out, const LinmathAxisAngle a0, const LinmathAxisAngle a1) {
	LinmathQuat q0, q1, qout;
	quatfromaxisangle(q0, a0, norm3d(a0));
	quatfromaxisangle(q1, a1, norm3d(a1));

	quatrotateabout(qout, q0, q1);
	FLT mag;
	axisanglefromquat(&mag, out, qout);
	scale3d(out, out, mag);
}
inline void quatrotateabout(LinmathQuat qout, const LinmathQuat q1, const LinmathQuat q2) {
	// NOTE: Does not normalize
	LinmathQuat rtn = {0};
	FLT *p = qout;
	bool aliased = q1 == qout || q2 == qout;
	if (aliased) {
		p = rtn;
	}

	p[0] = (q1[0] * q2[0]) - (q1[1] * q2[1]) - (q1[2] * q2[2]) - (q1[3] * q2[3]);
	p[1] = (q1[0] * q2[1]) + (q1[1] * q2[0]) + (q1[2] * q2[3]) - (q1[3] * q2[2]);
	p[2] = (q1[0] * q2[2]) - (q1[1] * q2[3]) + (q1[2] * q2[0]) + (q1[3] * q2[1]);
	p[3] = (q1[0] * q2[3]) + (q1[1] * q2[2]) - (q1[2] * q2[1]) + (q1[3] * q2[0]);

	if (aliased) {
		quatcopy(qout, rtn);
	}

	for (int i = 0; i < 4; i++)
		assert(!isnan(qout[i]));
}

inline void findnearestaxisanglemag(LinmathAxisAngleMag out, const LinmathAxisAngleMag inc,
									const LinmathAxisAngleMag match) {
	FLT norm_match = match ? norm3d(match) : 0;
	FLT norm_inc = norm3d(inc);
	FLT new_norm_inc = norm_inc;

	while (new_norm_inc > norm_match + M_PI) {
		new_norm_inc -= 2 * M_PI;
	}
	while (new_norm_inc + M_PI < norm_match) {
		new_norm_inc += 2 * M_PI;
	}
	scale3d(out, inc, new_norm_inc / norm_inc);
}

inline void quattoaxisanglemag(LinmathAxisAngleMag ang, const LinmathQuat q) {
	FLT axis_len = sqrt(q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
	FLT angle = 2. * atan2(axis_len, q[0]);
	scale3d(ang, q + 1, axis_len == 0 ? 0 : angle / axis_len);
}

inline void quatmultiplyrotation(LinmathQuat q, const LinmathQuat qv, FLT t) {
	LinmathAxisAngleMag aa;
	quattoaxisanglemag(aa, qv);
	scale3d(aa, aa, t);
	quatfromaxisanglemag(q, aa);
}

inline void quatscale(LinmathQuat qout, const LinmathQuat qin, FLT s) {
	qout[0] = qin[0] * s;
	qout[1] = qin[1] * s;
	qout[2] = qin[2] * s;
	qout[3] = qin[3] * s;
}

inline void quatdivs(LinmathQuat qout, const LinmathQuat qin, FLT s) {
	qout[0] = qin[0] / s;
	qout[1] = qin[1] / s;
	qout[2] = qin[2] / s;
	qout[3] = qin[3] / s;
}

FLT quatinnerproduct(const LinmathQuat qa, const LinmathQuat qb) {
	return (qa[0] * qb[0]) + (qa[1] * qb[1]) + (qa[2] * qb[2]) + (qa[3] * qb[3]);
}

inline void quatouterproduct(FLT *outvec3, LinmathQuat qa, LinmathQuat qb) {
	outvec3[0] = (qa[0] * qb[1]) - (qa[1] * qb[0]) - (qa[2] * qb[3]) + (qa[3] * qb[2]);
	outvec3[1] = (qa[0] * qb[2]) + (qa[1] * qb[3]) - (qa[2] * qb[0]) - (qa[3] * qb[1]);
	outvec3[2] = (qa[0] * qb[3]) - (qa[1] * qb[2]) + (qa[2] * qb[1]) - (qa[3] * qb[0]);
}

inline void quatevenproduct(LinmathQuat q, LinmathQuat qa, LinmathQuat qb) {
	q[0] = (qa[0] * qb[0]) - (qa[1] * qb[1]) - (qa[2] * qb[2]) - (qa[3] * qb[3]);
	q[1] = (qa[0] * qb[1]) + (qa[1] * qb[0]);
	q[2] = (qa[0] * qb[2]) + (qa[2] * qb[0]);
	q[3] = (qa[0] * qb[3]) + (qa[3] * qb[0]);
}

inline void quatoddproduct(FLT *outvec3, LinmathQuat qa, LinmathQuat qb) {
	outvec3[0] = (qa[2] * qb[3]) - (qa[3] * qb[2]);
	outvec3[1] = (qa[3] * qb[1]) - (qa[1] * qb[3]);
	outvec3[2] = (qa[1] * qb[2]) - (qa[2] * qb[1]);
}

inline void PoseSlerp(LinmathPose *out, const LinmathPose *start, const LinmathPose *end, FLT t) {
	LinmathPoint3d delta;
	sub3d(delta, end->Pos, start->Pos);
	scale3d(delta, delta, t);
	add3d(out->Pos, delta, start->Pos);

	quatslerp(out->Rot, start->Rot, end->Rot, t);
}
inline void quatslerp(LinmathQuat q, const LinmathQuat qa, const LinmathQuat qb, FLT t) {
	FLT an[4];
	FLT bn[4];
	quatnormalize(an, qa);
	quatnormalize(bn, qb);

	// Switched implementation to match https://en.wikipedia.org/wiki/Slerp

	// Compute the cosine of the angle between the two vectors.
	double dot = dot3d(qa, qb);

	LinmathQuat nqb;

	// If the dot product is negative, slerp won't take
	// the shorter path. Note that v1 and -v1 are equivalent when
	// the negation is applied to all four components. Fix by 
	// reversing one quaternion.
	if (dot < 0.0f) {
		quatscale( nqb, qb, -1 );
		dot = -dot;
	}
	else
	{
		quatscale( nqb, qb, 1 );
	}

	const double DOT_THRESHOLD = 0.9995;
	if (dot > DOT_THRESHOLD) {
		// If the inputs are too close for comfort, linearly interpolate
		// and normalize the result.

		//Quaternion result = v0 + t*(v1 - v0);
		LinmathQuat tmp;
		quatsub( tmp, nqb, qa );
		quatscale( tmp, tmp, t );
		quatadd( q, qa, tmp );
		quatnormalize( q, q );
		return;
	}

	// Since dot is in range [0, DOT_THRESHOLD], acos is safe
	double theta_0 = FLT_ACOS(dot);        // theta_0 = angle between input vectors
	double theta = theta_0*t;              // theta = angle between v0 and result
	double sin_theta = FLT_SIN(theta);     // compute this value only once
	double sin_theta_0 = FLT_SIN(theta_0); // compute this value only once

	double s0 = cos( theta ) - dot * sin_theta / sin_theta_0;  // == sin(theta_0 - theta) / sin(theta_0)
	double s1 = sin_theta / sin_theta_0;

	LinmathQuat aside;
	LinmathQuat bside;
	quatscale(bside, nqb, s1 );
	quatscale(aside, qa, s0 );
	quatadd(q, aside, bside);
	quatnormalize(q, q);
}

inline void eulerrotatevector(FLT *vec3out, const LinmathEulerAngle eulerAngle, const FLT *vec3in) {
	LinmathQuat q;
	quatfromeuler(q, eulerAngle);
	quatrotatevector(vec3out, q, vec3in);
}
inline void quatrotatevector(FLT *vec3out, const LinmathQuat quat, const FLT *vec3in) {
	// See: http://www.geeks3d.com/20141201/how-to-rotate-a-vertex-by-a-quaternion-in-glsl/

	FLT tmp[3];
	FLT tmp2[3];
	cross3d(tmp, &quat[1], vec3in);
	tmp[0] += vec3in[0] * quat[0];
	tmp[1] += vec3in[1] * quat[0];
	tmp[2] += vec3in[2] * quat[0];
	cross3d(tmp2, &quat[1], tmp);
	vec3out[0] = vec3in[0] + 2 * tmp2[0];
	vec3out[1] = vec3in[1] + 2 * tmp2[1];
	vec3out[2] = vec3in[2] + 2 * tmp2[2];
}

// Matrix Stuff

Matrix3x3 inverseM33(const Matrix3x3 mat) {
	Matrix3x3 newMat;
	for (int a = 0; a < 3; a++) {
		for (int b = 0; b < 3; b++) {
			newMat.val[a][b] = mat.val[a][b];
		}
	}

	for (int i = 0; i < 3; i++) {
		for (int j = i + 1; j < 3; j++) {
			FLT tmp = newMat.val[i][j];
			newMat.val[i][j] = newMat.val[j][i];
			newMat.val[j][i] = tmp;
		}
	}

	return newMat;
}

inline void rotation_between_vecs_to_m3(Matrix3x3 *m, const FLT v1[3], const FLT v2[3]) {
	FLT q[4];

	quatfrom2vectors(q, v1, v2);

	quattomatrix33(&(m->val[0][0]), q);
}

inline void rotate_vec(FLT *out, const FLT *in, Matrix3x3 rot) {
	out[0] = rot.val[0][0] * in[0] + rot.val[1][0] * in[1] + rot.val[2][0] * in[2];
	out[1] = rot.val[0][1] * in[0] + rot.val[1][1] * in[1] + rot.val[2][1] * in[2];
	out[2] = rot.val[0][2] * in[0] + rot.val[1][2] * in[1] + rot.val[2][2] * in[2];

	return;
}

// This function based on code from Object-oriented Graphics Rendering Engine
// Copyright(c) 2000 - 2012 Torus Knot Software Ltd
// under MIT license
// http://www.ogre3d.org/docs/api/1.9/_ogre_vector3_8h_source.html

inline void eulerfrom2vectors(LinmathEulerAngle euler, const FLT *src, const FLT *dest) {
	LinmathQuat q;
	quatfrom2vectors(q, src, dest);
	quattoeuler(euler, q);
}
/** Gets the shortest arc quaternion to rotate this vector to the destination
vector.
@remarks
If you call this with a dest vector that is close to the inverse
of this vector, we will rotate 180 degrees around a generated axis if
since in this case ANY axis of rotation is valid.
*/
inline void quatfrom2vectors(LinmathQuat q, const FLT *src, const FLT *dest) {
	// Based on Stan Melax's article in Game Programming Gems

	// Copy, since cannot modify local
	FLT v0[3];
	FLT v1[3];
	normalize3d(v0, src);
	normalize3d(v1, dest);

	FLT d = dot3d(v0, v1); // v0.dotProduct(v1);
	// If dot == 1, vectors are the same
	if (d >= 1.0f) {
		quatsetnone(q);
		return;
	}
	if (d < (1e-6f - 1.0f)) {
		// Generate an axis
		FLT unitX[3] = {1, 0, 0};
		FLT unitY[3] = {0, 1, 0};

		FLT axis[3];
		cross3d(axis, unitX, src);												  // pick an angle
		if ((axis[0] < 1.0e-35f) && (axis[1] < 1.0e-35f) && (axis[2] < 1.0e-35f)) // pick another if colinear
		{
			cross3d(axis, unitY, src);
		}
		normalize3d(axis, axis);
		quatfromaxisangle(q, axis, LINMATHPI);
	} else {
		FLT s = FLT_SQRT((1 + d) * 2);
		FLT invs = 1 / s;

		FLT c[3];
		cross3d(c, v0, v1);

		q[0] = s * 0.5f;
		q[1] = c[0] * invs;
		q[2] = c[1] * invs;
		q[3] = c[2] * invs;

		quatnormalize(q, q);
	}
}

inline void matrix44copy(FLT *mout, const FLT *minm) { memcpy(mout, minm, sizeof(FLT) * 16); }

inline void matrix44transpose(FLT *mout, const FLT *minm) {
	mout[0] = minm[0];
	mout[1] = minm[4];
	mout[2] = minm[8];
	mout[3] = minm[12];

	mout[4] = minm[1];
	mout[5] = minm[5];
	mout[6] = minm[9];
	mout[7] = minm[13];

	mout[8] = minm[2];
	mout[9] = minm[6];
	mout[10] = minm[10];
	mout[11] = minm[14];

	mout[12] = minm[3];
	mout[13] = minm[7];
	mout[14] = minm[11];
	mout[15] = minm[15];
}

inline void ApplyPoseToPoint(LinmathPoint3d pout, const LinmathPose *pose, const LinmathPoint3d pin) {
	LinmathPoint3d tmp;
	quatrotatevector(tmp, pose->Rot, pin);
	add3d(pout, tmp, pose->Pos);
	for (int i = 0; i < 3; i++)
		assert(!isnan(pout[i]));
}

inline void ApplyPoseToPose(LinmathPose *pout, const LinmathPose *lhs_pose, const LinmathPose *rhs_pose) {
	ApplyPoseToPoint(pout->Pos, lhs_pose, rhs_pose->Pos);
	quatrotateabout(pout->Rot, lhs_pose->Rot, rhs_pose->Rot);
	for (int i = 0; i < 3; i++)
		assert(!isnan(pout->Pos[i]));
}

inline void ApplyAxisAnglePoseToPoint(LinmathPoint3d pout, const LinmathAxisAnglePose *pose, const LinmathPoint3d pin) {
	LinmathPoint3d tmp;
	axisanglerotatevector(tmp, pose->AxisAngleRot, pin);
	add3d(pout, tmp, pose->Pos);
	for (int i = 0; i < 3; i++)
		assert(isfinite(pout[i]));
}

inline void ApplyAxisAngleVelocity(LinmathAxisAnglePose *p_1, FLT t, const LinmathAxisAnglePose *p_0,
								   const LinmathAxisAngleVelocity *vel) {
	LinmathVec3d posDisplacement, velDisplacement;
	scale3d(posDisplacement, vel->Pos, t);
	scale3d(velDisplacement, vel->AxisAngleRot, t);

	add3d(p_1->Pos, p_0->Pos, posDisplacement);
	add3d(p_1->AxisAngleRot, p_0->AxisAngleRot, velDisplacement);
}
inline void ApplyAxisAnglePoseToPose(LinmathAxisAnglePose *pout, const LinmathAxisAnglePose *lhs_pose,
									 const LinmathAxisAnglePose *rhs_pose) {
	ApplyAxisAnglePoseToPoint(pout->Pos, lhs_pose, rhs_pose->Pos);
	axisanglerotateabout(pout->AxisAngleRot, lhs_pose->AxisAngleRot, rhs_pose->AxisAngleRot);
	for (int i = 0; i < 3; i++)
		assert(!isnan(pout->Pos[i]));
}
inline void InvertAAPose(LinmathAxisAnglePose *poseout, const LinmathAxisAnglePose *pose) {
	scale3d(poseout->AxisAngleRot, pose->AxisAngleRot, -1);

	axisanglerotatevector(poseout->Pos, poseout->AxisAngleRot, pose->Pos);
	scale3d(poseout->Pos, poseout->Pos, -1);
}
inline void InvertPose(LinmathPose *poseout, const LinmathPose *pose) {
	quatgetreciprocal(poseout->Rot, pose->Rot);

	quatrotatevector(poseout->Pos, poseout->Rot, pose->Pos);
	scale3d(poseout->Pos, poseout->Pos, -1);
}

inline void PoseToMatrix(FLT *matrix44, const LinmathPose *pose_in) {
	quattomatrix(matrix44, pose_in->Rot);

	/*
	matrix44[12] = pose_in->Pos[0];
	matrix44[13] = pose_in->Pos[1];
	matrix44[14] = pose_in->Pos[2];
	*/
	matrix44[3] = pose_in->Pos[0];
	matrix44[7] = pose_in->Pos[1];
	matrix44[11] = pose_in->Pos[2];
}
void KabschCentered(LinmathQuat qout, const FLT *ptsA, const FLT *ptsB, int num_pts) {
	KabschCenteredScaled(qout, 0, ptsA, ptsB, num_pts);
}
void KabschCenteredScaled(LinmathQuat qout, FLT *scale, const FLT *ptsA, const FLT *ptsB, int num_pts) {
	// Note: The following follows along with https://en.wikipedia.org/wiki/Kabsch_algorithm
	// for the most part but we use some transpose identities to avoid unneeded transposes
	CN_CREATE_STACK_MAT(A, num_pts, 3); // (FLT *)ptsA);
	cn_row_major_to_internal(&A, ptsA);
	CN_CREATE_STACK_MAT(B, num_pts, 3);
	cn_row_major_to_internal(&B, ptsB);

	FLT _C[9] = {0};
	CnMat C = cnMat(3, 3, _C);
	cnGEMM(&B, &A, 1, 0, 0, &C, CN_GEMM_FLAG_A_T);

	FLT _U[9] = {0};
	FLT _W[9] = {0};
	FLT _VT[9] = {0};
	CnMat U = cnMat(3, 3, _U);
	CnMat W = cnMat(3, 3, _W);
	CnMat VT = cnMat(3, 3, _VT);

	cnSVD(&C, &W, &U, &VT, CN_SVD_V_T | CN_SVD_MODIFY_A);

	CN_CREATE_STACK_MAT(R, 3, 3);
	cnGEMM(&U, &VT, 1, 0, 0, &R, 0);

	// Enforce RH rule
	if (cnDet(&R) < 0.) {
		for (int i = 0; i < 3; i++)
			cnMatrixSet(&U, i, 2, -cnMatrixGet(&U, i, 2));
		cnGEMM(&U, &VT, 1, 0, 0, &R, 0);
	}

	if (scale) {
		*scale = 0;
		for (int i = 0; i < num_pts; i++) {
			*scale += norm3d(ptsA + 3 * i) / norm3d(ptsB + 3 * i);
		}
		*scale = *scale / num_pts;
	}

	quatfromcnMatrix(qout, &R);

	CN_FREE_STACK_MAT(R);
	CN_FREE_STACK_MAT(B);
	CN_FREE_STACK_MAT(A);
}

LINMATH_EXPORT void KabschScaled(LinmathPose *B2Atx, FLT *scale, const FLT *_ptsA, const FLT *_ptsB, int num_pts) {
	FLT centerA[3];
	FLT centerB[3];

	FLT *ptsA = alloca(num_pts * 3 * sizeof(FLT));
	FLT *ptsB = alloca(num_pts * 3 * sizeof(FLT));

	center3d(ptsA, centerA, _ptsA, num_pts);
	center3d(ptsB, centerB, _ptsB, num_pts);

	KabschCenteredScaled(B2Atx->Rot, scale, ptsA, ptsB, num_pts);
	quatrotatevector(centerA, B2Atx->Rot, centerA);
	sub3d(B2Atx->Pos, centerB, centerA);
}
LINMATH_EXPORT void KabschPoses(LinmathPose *A2Btx, const LinmathPose *posesA, const LinmathPose *posesB,
								int num_poses) {
	LinmathVec3d pts[] = {
		{1, 0, 0},
		{0, 1, 0},
		{0, 0, 1},
	};

	int num_pts = num_poses * sizeof(pts) / sizeof(pts[0]);
	FLT *ptsA = alloca(num_pts * 3 * sizeof(FLT));
	FLT *ptsB = alloca(num_pts * 3 * sizeof(FLT));

	int num_pairs = 0;
	for (int j = 0; j < num_poses; j++) {
		if (quatiszero(posesB[j].Rot) || quatiszero(posesA[j].Rot))
			continue;

		for (int h = 0; h < sizeof(pts) / sizeof(pts[0]); h++) {
			ApplyPoseToPoint(ptsA + num_pairs * 3, &posesA[j], pts[h]);
			ApplyPoseToPoint(ptsB + num_pairs * 3, &posesB[j], pts[h]);
			num_pairs++;
		}
	}
	*A2Btx = (LinmathPose){0};
	if (num_pairs > 0) {
		Kabsch(A2Btx, ptsA, ptsB, num_pairs);
	}
}
LINMATH_EXPORT void Kabsch(LinmathPose *B2Atx, const FLT *_ptsA, const FLT *_ptsB, int num_pts) {
	KabschScaled(B2Atx, 0, _ptsA, _ptsB, num_pts);
}
LINMATH_EXPORT LinmathQuat LinmathQuat_Identity = {1.0};
LINMATH_EXPORT LinmathPose LinmathPose_Identity = {.Rot = {1.0}};

inline FLT linmath_rand(FLT min, FLT max) {
	FLT r = rand() / (FLT)RAND_MAX;
	r *= (max - min);
	r += min;
	return r;
}

LINMATH_EXPORT FLT linmath_norm_pdf(FLT v, FLT mean, FLT std) {
	const FLT scale = 1. / sqrt(M_PI * 2) / std;
	FLT ratio = (v - mean) / std;
	ratio = (ratio * ratio) * -.5;
	return scale * exp(ratio);
}

#ifndef M_SQRT1_2
#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
#endif
LINMATH_EXPORT FLT linmath_chauvenet_criterion(FLT v, FLT mu, FLT sigma, int n) {
	return erfcf((fabs(v - mu) / sigma) * M_SQRT1_2) * n;
}

FLT linmath_normrand(FLT mu, FLT sigma) {
	static const double epsilon = 0.0000001;

	static double z1;
	static bool generate;
	generate = !generate;

	if (!generate)
		return z1 * sigma + mu;

	double u1, u2;
	do {
		u1 = rand() * (1.0 / RAND_MAX);
		u2 = rand() * (1.0 / RAND_MAX);
	} while (u1 <= epsilon);

	double z0;
	z0 = sqrt(-2.0 * log(u1)) * cos(LINMATHPI * 2. * u2);
	z1 = sqrt(-2.0 * log(u1)) * sin(LINMATHPI * 2. * u2);
	return z0 * sigma + mu;
}

#ifndef CN_MATRIX_IS_COL_MAJOR
#define RM_IDX(row, col, stride) (col + (row * stride))
#else
#define RM_IDX(row, col, stride) (row + (col * stride))
#endif

#ifdef _MSC_VER
#define RESTRICT_KEYWORD
#else
#define RESTRICT_KEYWORD restrict
#endif

static inline void sparse_multiply_dense_by_sparse_t_to_sym(struct CnMat *out, const CnMat *lhs,
															const struct sparse_matrix *rhs, const CnMat *aug) {
	int16_t m = lhs->rows;
	int16_t k = rhs->cols;
	int16_t n = rhs->rows;
	assert(lhs->cols == rhs->cols);
	assert(out->rows == lhs->rows);
	assert(out->cols == rhs->rows);
	assert(out->cols == out->rows);

	const FLT *RESTRICT_KEYWORD A = CN_FLT_PTR(lhs);
	FLT *RESTRICT_KEYWORD C = CN_FLT_PTR(out);

	if (aug == 0) {
		for (int i = 0; i < m * n; i++)
			C[i] = 0;
	} else {
		memcpy(C, CN_FLT_PTR(aug), sizeof(FLT) * m * n);
	}

	const int16_t *RESTRICT_KEYWORD row_index = rhs->row_index;
	const int16_t *RESTRICT_KEYWORD col_index = rhs->col_index;
	const FLT *RESTRICT_KEYWORD data = rhs->data;
	int16_t out_stride = cn_stride(out);
	int_fast16_t rhs_stride = cn_stride(lhs);

	for (int i = 0; i < n; i++) {
		int row_start = row_index[i];
		int row_end = row_index[i + 1];

		for (int z = row_start; z < row_end; z++) {
			int p = col_index[z];
			FLT v = data[z]; // B(i, p)

			for (int j = i; j < m; j++) {
				FLT r = A[RM_IDX(j, p, rhs_stride)]; // A(j, p)

				FLT add = v * r;
				C[RM_IDX(i, j, out_stride)] += add;
				if (i != j)
					C[RM_IDX(j, i, out_stride)] += add;
			}
		}
	}
}

static inline void sparse_multiply_sparse_by_dense_sym(struct CnMat *out, const struct sparse_matrix *lhs,
													   const CnMat *rhs) {
	int16_t m = lhs->rows;
	int16_t n = rhs->cols;
	assert(lhs->cols == rhs->rows);
	assert(out->rows == lhs->rows);
	assert(out->cols == rhs->cols);
	assert(rhs->cols == rhs->rows);

	const FLT *RESTRICT_KEYWORD B = CN_FLT_PTR(rhs);
	FLT *RESTRICT_KEYWORD C = CN_FLT_PTR(out);

	for (int i = 0; i < m * n; i++)
		C[i] = 0;

	const int16_t *RESTRICT_KEYWORD row_index = lhs->row_index;
	const int16_t *RESTRICT_KEYWORD col_index = lhs->col_index;
	const FLT *RESTRICT_KEYWORD data = lhs->data;
	int16_t out_stride = cn_stride(out);
	int_fast16_t rhs_stride = cn_stride(rhs);

	for (int i = 0; i < m; i++) {
		int row_start = row_index[i];
		int row_end = row_index[i + 1];

		for (int z = row_start; z < row_end; z++) {
			for (int j = 0; j < n; j++) {

				int p = col_index[z];
				FLT v = data[z];				   // A(i, p)
				FLT r = B[RM_IDX(p, j, rhs_stride)]; // B(p, j)

				C[RM_IDX(i, j, out_stride)] += v * r;
			}
		}
	}
}

static inline size_t create_sparse_matrix(struct sparse_matrix *out, const struct CnMat *in) {
	int16_t *col_idxs = out->col_index;
	int16_t *row_idxs = out->row_index;
	size_t idx = 0;
	FLT *data = out->data;
	memset(data, 0, sizeof(FLT) * out->rows * out->cols);
	memset(out->row_index, -1, sizeof(uint16_t) * (out->rows + 1));
	memset(out->col_index, -1, sizeof(uint16_t) * (out->rows * out->cols));

	const FLT *RESTRICT_KEYWORD input = CN_FLT_PTR(in);
	for (int i = 0; i < in->rows; i++) {
		*(row_idxs++) = idx;
		for (int j = 0; j < in->cols; j++) {
			FLT v = input[RM_IDX(i, j, cn_stride(in))];
			if (fabs(v) > 1e-10) {
				idx++;
				*(col_idxs++) = j;
				*(data++) = v;
			}
		}
	}
	*(row_idxs++) = idx;
	return idx;
}

inline void gemm_ABAt_add(struct CnMat *out, const struct CnMat *A, const struct CnMat *B, const struct CnMat *C) {
	gemm_ABAt_add_scaled(out, A, B, C, 1, 1, 1);
}
inline void gemm_ABAt_add_scaled(struct CnMat *out, const struct CnMat *A, const struct CnMat *B, const struct CnMat *C,
								 FLT scale_A, FLT scale_B, FLT scale_C) {
	CN_CREATE_STACK_MAT(tmp, A->rows, B->cols);
	cnGEMM(A, B, scale_A * scale_B, 0, 0, &tmp, 0);
	cnGEMM(&tmp, A, 1, C, scale_C, out, CN_GEMM_FLAG_B_T);
	CN_FREE_STACK_MAT(tmp);
}
void matrix_ABAt_add(struct CnMat *out, const struct CnMat *A, const struct CnMat *B, const struct CnMat *C) {
	ALLOC_SPARSE_MATRIX(s, A->rows, A->cols);
	size_t nonzeros = create_sparse_matrix(&s, A);

	CN_CREATE_STACK_MAT(tmp, s.rows, B->cols);
	sparse_multiply_sparse_by_dense_sym(&tmp, &s, B);
	sparse_multiply_dense_by_sparse_t_to_sym(out, &tmp, &s, C);
	CN_FREE_STACK_MAT(tmp);
}

void linmath_get_line_dir(LinmathPoint3d dir, const struct LinmathLine3d *ray) {
	sub3d(dir, ray->b, ray->a);
	normalize3d(dir, dir);
}

void linmath_find_best_intersection(LinmathPoint3d pt, const struct LinmathLine3d *lines, size_t num) {
	// http://mathforum.org/library/drmath/view/69105.html
	CN_CREATE_STACK_MAT(A, num * 2, 3);
	CN_CREATE_STACK_MAT(B, num * 2, 1);

	for (int i = 0; i < num; i++) {
		LinmathPoint3d dir;
		linmath_get_line_dir(dir, &lines[i]);

		// https://stackoverflow.com/a/33658815
		LinmathPoint3d n1 = {0, 0, 1};
		LinmathPoint3d tmp;
		scale3d(tmp, dir, dot3d(n1, dir));
		sub3d(n1, n1, tmp);

		LinmathPoint3d n2;
		cross3d(n2, n1, dir);

		assert(fabs(dot3d(dir, n1)) < 1e-6);
		assert(fabs(dot3d(dir, n2)) < 1e-6);
		assert(fabs(dot3d(n1, n2)) < 1e-6);

		FLT d1 = dot3d(n1, lines[i].a);
		FLT d2 = dot3d(n2, lines[i].a);

		for (int j = 0; j < 3; j++) {
			cnMatrixSet(&A, i * 2 + 0, j, n1[j]);
			cnMatrixSet(&B, i * 2 + 0, 0, d1);

			cnMatrixSet(&A, i * 2 + 1, j, n2[j]);
			cnMatrixSet(&B, i * 2 + 1, 0, d2);
		}
	}

	CnMat x = cnMat(3, 1, pt);
	cnSolve(&A, &B, &x, CN_INVERT_METHOD_SVD);

	CN_FREE_STACK_MAT(B);
	CN_FREE_STACK_MAT(A);
}

void linmath_pt_along_line(LinmathPoint3d pt, const struct LinmathLine3d *ray, FLT t) {
	LinmathPoint3d rayDirection;
	linmath_get_line_dir(rayDirection, ray);
	scale3d(pt, rayDirection, t);
	add3d(pt, ray->a, pt);
}

FLT linmath_point_distance_from_line(struct LinmathLine3d *ray, LinmathVec3d pt, FLT *t) {
	LinmathVec3d pt_a, pt_b, b_a;
	sub3d(pt_a, pt, ray->a);
	sub3d(pt_b, pt, ray->b);
	sub3d(b_a, ray->b, ray->a);

	LinmathVec3d pt_cross;
	cross3d(pt_cross, pt_a, pt_b);

	if (t) {
		LinmathVec3d a_pt;
		sub3d(a_pt, ray->a, pt);

		*t = -dot3d(a_pt, b_a) / norm3d(b_a) / norm3d(b_a);
	}
	return norm3d(pt_cross) / norm3d(b_a);
}
