/*
	oh4_easing.h - v1.0 - public domain
	Authored 2023 by Eric Scrivner
	no warranty implied; use at your own risk

	Before including,

		#define OH4_EASING_IMPLEMENTATION

	in the file that you want to have the implementation.

ABOUT:

	This header file is a library of common easing functions along with some
	basic quality of life helpers.

	The mathematical expressions of some functions have been simplified,
	others have not but probably could be. In general, the straightforward
	implementation of each easing function is chosen. No further optimizations
	have been made.

USAGE:

	The library provides the following functions for in, out, and in-out easing.
	These functions take a t value in range [0.0f, 1.0f] and return a value in
	range [0.0f, 1.0f]:

		float OH4EaseInSin(float t)
		float OH4EaseOutSin(float t)
		float OH4EaseInOutSin(float t)

		float OH4EaseInQuad(float t)
		float OH4EaseOutQuad(float t)
		float OH4EaseInOutQuad(float t)

		float OH4EaseInCubic(float t)
		float OH4EaseOutCubic(float t)
		float OH4EaseInOutCubic(float t)

		float OH4EaseInQuart(float t)
		float OH4EaseOutQuart(float t)
		float OH4EaseInOutQuart(float t)

		float OH4EaseInQuint(float t)
		float OH4EaseOutQuint(float t)
		float OH4EaseInOutQuint(float t)
		
		float OH4EaseInExpo(float t)
		float OH4EaseOutExpo(float t)
		float OH4EaseInOutExpo(float t)

		float OH4EaseInCirc(float t)
		float OH4EaseOutCirc(float t)
		float OH4EaseInOutCirc(float t)

		float OH4EaseInBack(float t)
		float OH4EaseOutBack(float t)
		float OH4EaseInOutBack(float t)

		float OH4EaseInElastic(float t)
		float OH4EaseOutElastic(float t)
		float OH4EaseInOutElastic(float t)

		float OH4EaseInBounce(float t)
		float OH4EaseOutBounce(float t)
		float OH4EaseInOutBounce(float t)

	There is also a set of enumerated names for these easings, a function for
	retrieving an easing function pointer by enumerated name, and a function for
	retrieving the English ASCII string name corresponding to an enumerated
	value:

		The enumerations have the form OH4EASING_*
			Ex: OH4EASING_in_elastic, OH4EASING_out_quintic, OH4EASING_inout_sin

		OH4EasingFn* OH4EasingFnFromEnum(int easing_enum) // Returns a pointer to the easing function, 0 if invalid
		char*        OH4EasingNameFromEnum(int easing_enum) // Returns the name of the easing function, 0 if invalid

	The inclusion of the OH4EASING_* enums and the two functions above can be
	disabled by adding the following:

		#define OH4EASING_WITH_ENUMS 0

*/
#ifndef OH4_EASING_H
#define OH4_EASING_H

#ifndef OH4EASING_DEF
	#ifdef OH4_EASING_STATIC
		#define OH4EASING_DEF static
	#else
		#ifdef __cplusplus
			#define OH4EASING_DEF extern "C"
		#else
			#define OH4EASING_DEF extern
		#endif
	#endif
#endif

#ifndef OH4_EASING_WITH_ENUMS
	#define OH4_EASING_WITH_ENUMS 1
#endif

OH4EASING_DEF float			OH4EaseInSin(float t);
OH4EASING_DEF float			OH4EaseOutSin(float t);
OH4EASING_DEF float			OH4EaseInOutSin(float t);

OH4EASING_DEF float			OH4EaseInQuad(float t);
OH4EASING_DEF float			OH4EaseOutQuad(float t);
OH4EASING_DEF float			OH4EaseInOutQuad(float t);

OH4EASING_DEF float			OH4EaseInCubic(float t);
OH4EASING_DEF float			OH4EaseOutCubic(float t);
OH4EASING_DEF float			OH4EaseInOutCubic(float t);

OH4EASING_DEF float			OH4EaseInQuart(float t);
OH4EASING_DEF float			OH4EaseOutQuart(float t);
OH4EASING_DEF float			OH4EaseInOutQuart(float t);

OH4EASING_DEF float			OH4EaseInQuint(float t);
OH4EASING_DEF float			OH4EaseOutQuint(float t);
OH4EASING_DEF float			OH4EaseInOutQuint(float t);

OH4EASING_DEF float			OH4EaseInExpo(float t);
OH4EASING_DEF float			OH4EaseOutExpo(float t);
OH4EASING_DEF float			OH4EaseInOutExpo(float t);

OH4EASING_DEF float			OH4EaseInCirc(float t);
OH4EASING_DEF float			OH4EaseOutCirc(float t);
OH4EASING_DEF float			OH4EaseInOutCirc(float t);

OH4EASING_DEF float			OH4EaseInBack(float t);
OH4EASING_DEF float			OH4EaseOutBack(float t);
OH4EASING_DEF float			OH4EaseInOutBack(float t);

OH4EASING_DEF float			OH4EaseInElastic(float t);
OH4EASING_DEF float			OH4EaseOutElastic(float t);
OH4EASING_DEF float			OH4EaseInOutElastic(float t);

OH4EASING_DEF float			OH4EaseInBounce(float t);
OH4EASING_DEF float			OH4EaseOutBounce(float t);
OH4EASING_DEF float			OH4EaseInOutBounce(float t);

#if OH4_EASING_WITH_ENUMS

typedef float (OH4EasingFn)(float);

enum {
	OH4EASING_in_sin,
	OH4EASING_out_sin,
	OH4EASING_inout_sin,
	OH4EASING_in_quad,
	OH4EASING_out_quad,
	OH4EASING_inout_quad,
	OH4EASING_in_cubic,
	OH4EASING_out_cubic,
	OH4EASING_inout_cubic,
	OH4EASING_in_quart,
	OH4EASING_out_quart,
	OH4EASING_inout_quart,
	OH4EASING_in_quint,
	OH4EASING_out_quint,
	OH4EASING_inout_quint,
	OH4EASING_in_expo,
	OH4EASING_out_expo,
	OH4EASING_inout_expo,
	OH4EASING_in_circ,
	OH4EASING_out_circ,
	OH4EASING_inout_circ,
	OH4EASING_in_back,
	OH4EASING_out_back,
	OH4EASING_inout_back,
	OH4EASING_in_elastic,
	OH4EASING_out_elastic,
	OH4EASING_inout_elastic,
	OH4EASING_in_bounce,
	OH4EASING_out_bounce,
	OH4EASING_inout_bounce,
	OH4EASING_COUNT
};

OH4EASING_DEF OH4EasingFn*	OH4EasingFnFromEnum(int easing_enum);
OH4EASING_DEF const char*	OH4EasingNameFromEnum(int easing_enum);

#endif /* OH4_EASING_WITH_ENUMS */

#endif /* OH4_EASING_H */

#ifdef OH4_EASING_IMPLEMENTATION

#include <math.h>

#ifndef OH4Sin
	#define OH4Sin(x) sinf(x)
#endif

#ifndef OH4Cos
	#define OH4Cos(x) cosf(x)
#endif

#ifndef OH4Pow
	#define OH4Pow(x, e) powf(x, e)
#endif

#ifndef OH4Sqrt
	#define OH4Sqrt(x) sqrtf(x)
#endif

#ifndef OH4Abs
	#define OH4Abs(x) fabsf(x)
#endif

#ifndef OH4_PI
	#define OH4_PI 3.141592f
#endif

#ifndef OH4_HALFPI
	#define OH4_HALFPI 1.570796f
#endif

OH4EASING_DEF float OH4EaseInSin(float t) {
	return(OH4Sin(OH4_HALFPI * t));
}

OH4EASING_DEF float OH4EaseOutSin(float t) {
	return(1.0f + OH4Sin((--t) * OH4_HALFPI));
}

OH4EASING_DEF float OH4EaseInOutSin(float t) {
	return(0.5f * (1.0f + OH4Sin((t - 0.5f) * OH4_PI)));
}

OH4EASING_DEF float OH4EaseInQuad(float t) {
	return(t * t);
}

OH4EASING_DEF float OH4EaseOutQuad(float t) {
	return(t * (2.0f - t));
}

OH4EASING_DEF float OH4EaseInOutQuad(float t) {
	return((t < 0.5f) ? (2.0f * t * t) : (t * (4.0f - 2.0f * t) - 1.0f));
}

OH4EASING_DEF float OH4EaseInCubic(float t) {
	return(t * t * t);
}

OH4EASING_DEF float OH4EaseOutCubic(float t) {
	return(1.0f - OH4Pow(1.0f - t, 3.0f));
}

OH4EASING_DEF float OH4EaseInOutCubic(float t) {
	return((t < 0.5f) ? (4.0f * t * t * t) : (1.0f - OH4Pow(-2.0f * t + 2.0f, 3.0f) / 2.0f));
}

OH4EASING_DEF float OH4EaseInQuart(float t) {
	return(t * t * t * t);
}

OH4EASING_DEF float	OH4EaseOutQuart(float t) {
	return(1.0f - OH4Pow(1.0f - t, 4.0f));
}

OH4EASING_DEF float	OH4EaseInOutQuart(float t) {
	if (t < 0.5f) {
		t *= t;
		return(8.0f * t * t);
	} else {
		return(1.0f - OH4Pow(-2.0f * t + 2.0f, 4) / 2.0f);
	}
}

OH4EASING_DEF float	OH4EaseInQuint(float t) {
	float t2 = t * t;
	return(t * t2 * t2);
}

OH4EASING_DEF float	OH4EaseOutQuint(float t) {
	return(1.0f - OH4Pow(1.0f - t, 5.0f));
}

OH4EASING_DEF float	OH4EaseInOutQuint(float t) {
	float t2;
	if (t < 0.5f) {
		t2 = t * t;
		return(16.0f * t * t2 * t2);
	} else {
		return(1.0f - OH4Pow(-2.0f * t + 2.0f, 5.0f) / 2.0f);
	}
}

OH4EASING_DEF float OH4EaseInExpo(float t) {
	return((t == 0.0f) ? 0.0f : OH4Pow(2.0f, 10.0f * t - 10.0f));
}

OH4EASING_DEF float OH4EaseOutExpo(float t) {
	return((t == 1.0f) ? 1.0f : (1.0f -  OH4Pow(2.0f, -10.0f * t)));
}

OH4EASING_DEF float OH4EaseInOutExpo(float t) {
	return((t == 0.0f) ? 0.0f : ((t == 1.0f) ? 1.0f : ((t < 0.5f) ? (OH4Pow(2.0f, 20.0f * t - 10.0f) / 2.0f) : ((2.0f - OH4Pow(2.0f, -20.0f * t + 10.f)) / 2.0f))));
}

OH4EASING_DEF float	OH4EaseInCirc(float t) {
	return(1.0f - OH4Sqrt(1.0f - t * t));
}

OH4EASING_DEF float	OH4EaseOutCirc(float t) {
	return(OH4Sqrt(1.0f - OH4Pow(t - 1.0f, 2.0f)));
}

OH4EASING_DEF float	OH4EaseInOutCirc(float t) {
	if (t < 0.5f) {
		return((1.0f - OH4Sqrt(1.0f - OH4Pow(2.0f * t, 2.0f))) / 2.0f);
	} else {
		return((OH4Sqrt(1.0f - OH4Pow(-2.0f * t + 2.0f, 2.0f)) + 1.0f) / 2.0f);
	}
}

OH4EASING_DEF float	OH4EaseInBack(float t) {
	return(t * t * (2.70158f * t - 1.70158f));
}

OH4EASING_DEF float	OH4EaseOutBack(float t) {
	return(1.0f + 2.70158f * OH4Pow(t - 1.0f, 3.0f) + 1.70158f * OH4Pow(t - 1.0f, 2.0f));
}

OH4EASING_DEF float	OH4EaseInOutBack(float t) {
	if (t < 0.5f) {
		return((OH4Pow(2.0f * t, 2.0f) * ((3.5949f * 2.0f * t - 2.5949f))) / 2.0f);
	} else {
		return((OH4Pow(2.0f * t - 2.0f, 2.0f) * (3.5949f * (t * 2.0f - 2.0f) + 2.5949f) + 2.0f) / 2.0f);
	}
}

OH4EASING_DEF float	OH4EaseInElastic(float t) {
	float t2 = t * t;
	return(t2 * t2 * OH4Sin(t * OH4_PI * 4.5f));
}

OH4EASING_DEF float	OH4EaseOutElastic(float t) {
	float t2 = (t - 1.0f) * (t - 1.0f);
	return(1.0f - t2 * t2 * OH4Cos(t * OH4_PI * 4.5f));
}

OH4EASING_DEF float	OH4EaseInOutElastic(float t) {
	float t2;
	if (t < 0.45f) {
		t2 = t * t;
		return(8.0f * t2 * t2 * OH4Sin(t * OH4_PI * 9.0f));
	} else if (t < 0.55f) {
		return(0.5f + 0.75f * OH4Sin(t * OH4_PI * 4.0f));
	} else {
		t2 = (t - 1.0f) * (t - 1.0f);
		return(1.0f - 8.0f * t2 * t2 * OH4Sin(t * OH4_PI * 9.0f));
	}
}

OH4EASING_DEF float	OH4EaseInBounce(float t) {
	return(OH4Pow(2.0f, 6.0f * (t - 1.0f)) * OH4Abs(OH4Sin(t * OH4_PI * 3.5f)));
}

OH4EASING_DEF float	OH4EaseOutBounce(float t) {
	return(1.0f - OH4Pow(2.0f, -6.0f * t) * OH4Abs(OH4Cos(t * OH4_PI * 3.5f)));
}

OH4EASING_DEF float	OH4EaseInOutBounce(float t) {
	if (t < 0.5f) {
		return(8.0f * OH4Pow(2.0f, 8.0f * (t - 1.0f)) * OH4Abs(OH4Sin(t * OH4_PI * 7.0f)));
	} else {
		return(1.0f - 8.0f * OH4Pow(2.0f, -8.0f * t) * OH4Abs(OH4Sin(t * OH4_PI * 7.0f)));
	}
}

#if OH4_EASING_WITH_ENUMS

OH4EASING_DEF OH4EasingFn* OH4EasingFnFromEnum(int easing_enum) {
	switch (easing_enum) {
		default: return(0);
		case OH4EASING_in_sin: return(OH4EaseInSin);
		case OH4EASING_out_sin: return(OH4EaseOutSin);
		case OH4EASING_inout_sin: return(OH4EaseInOutSin);
		case OH4EASING_in_quad: return(OH4EaseInQuad);
		case OH4EASING_out_quad: return(OH4EaseOutQuad);
		case OH4EASING_inout_quad: return(OH4EaseInOutQuad);
		case OH4EASING_in_cubic: return(OH4EaseInCubic);
		case OH4EASING_out_cubic: return(OH4EaseOutCubic);
		case OH4EASING_inout_cubic: return(OH4EaseInOutCubic);
		case OH4EASING_in_quart: return(OH4EaseInQuart);
		case OH4EASING_out_quart: return(OH4EaseOutQuart);
		case OH4EASING_inout_quart: return(OH4EaseInOutQuart);
		case OH4EASING_in_quint: return(OH4EaseInQuint);
		case OH4EASING_out_quint: return(OH4EaseOutQuint);
		case OH4EASING_inout_quint: return(OH4EaseInOutQuint);
		case OH4EASING_in_expo: return(OH4EaseInExpo);
		case OH4EASING_out_expo: return(OH4EaseOutExpo);
		case OH4EASING_inout_expo: return(OH4EaseInOutExpo);
		case OH4EASING_in_circ: return(OH4EaseInCirc);
		case OH4EASING_out_circ: return(OH4EaseOutCirc);
		case OH4EASING_inout_circ: return(OH4EaseInOutCirc);
		case OH4EASING_in_back: return(OH4EaseInBack);
		case OH4EASING_out_back: return(OH4EaseOutBack);
		case OH4EASING_inout_back: return(OH4EaseInOutBack);
		case OH4EASING_in_elastic: return(OH4EaseInElastic);
		case OH4EASING_out_elastic: return(OH4EaseOutElastic);
		case OH4EASING_inout_elastic: return(OH4EaseInOutElastic);
		case OH4EASING_in_bounce: return(OH4EaseInBounce);
		case OH4EASING_out_bounce: return(OH4EaseOutBounce);
		case OH4EASING_inout_bounce: return(OH4EaseInOutBounce);
	}
}

static const char* OH4EasingNameLUT[OH4EASING_COUNT] = {
	"InSin", "OutSin", "InOutSin",
	"InQuad", "OutQuad", "InOutQuad",
	"InCubic", "OutCubic", "InOutCubic",
	"InQuart", "OutQuart", "InOutQuart",
	"InQuint", "OutQuint", "InOutQuint",
	"InExpo", "OutExpo", "InOutExpo",
	"InCirc", "OutCirc", "InOutCirc",
	"InBack", "OutBack", "InOutBack",
	"InElastic", "OutElastic", "InOutElastic",
	"InBounce", "OutBounce", "InOutBounce"
};

OH4EASING_DEF const char* OH4EasingNameFromEnum(int easing_enum) {
	if (easing_enum >= 0 && easing_enum < OH4EASING_COUNT) {
		return(OH4EasingNameLUT[easing_enum]);
	}

	return(0);
}

#endif /* OH4_EASING_WITH_ENUMS */

#endif /* OH4_EASING_IMPLEMENTATION */
