2024-05-08 22:43:27 +04:00

519 lines
16 KiB
Plaintext

#include "Vectors.hpp"
#include "VectorsCocoa.h"
#include "Lottie/Public/Keyframes/Interpolatable.hpp"
#include <math.h>
#import <QuartzCore/QuartzCore.h>
#import <simd/simd.h>
namespace lottie {
CATransform3D CATransform3D::_identity = CATransform3D(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
double interpolate(double value, double to, double amount) {
return value + ((to - value) * amount);
}
Vector1D interpolate(
Vector1D const &from,
Vector1D const &to,
double amount
) {
return Vector1D(interpolate(from.value, to.value, amount));
}
Vector2D interpolate(
Vector2D const &from,
Vector2D const &to,
double amount
) {
return Vector2D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount));
}
Vector3D interpolate(
Vector3D const &from,
Vector3D const &to,
double amount
) {
return Vector3D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount), interpolate(from.z, to.z, amount));
}
static double cubicRoot(double value) {
return pow(value, 1.0 / 3.0);
}
static double SolveQuadratic(double a, double b, double c) {
double result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
result = (-b - sqrt((b * b) - 4 * a * c)) / (2 * a);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
return -1.0;
}
static double SolveCubic(double a, double b, double c, double d) {
if (a == 0.0) {
return SolveQuadratic(b, c, d);
}
if (d == 0.0) {
return 0.0;
}
b /= a;
c /= a;
d /= a;
double q = (3.0 * c - (b * b)) / 9.0;
double r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0;
double disc = (q * q * q) + (r * r);
double term1 = b / 3.0;
if (disc > 0.0) {
double s = r + sqrt(disc);
s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s);
double t = r - sqrt(disc);
t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t);
double result = -term1 + s + t;
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
} else if (disc == 0) {
double r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r);
double result = -term1 + 2.0 * r13;
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
result = -(r13 + term1);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
} else {
q = -q;
double dum1 = q * q * q;
dum1 = acos(r / sqrt(dum1));
double r13 = 2.0 * sqrt(q);
double result = -term1 + r13 * cos(dum1 / 3.0);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0);
if (isInRangeOrEqual(result, 0.0, 1.0)) {
return result;
}
}
return -1;
}
double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) {
double t = 0.0;
if (value == P0.x) {
// Handle corner cases explicitly to prevent rounding errors
t = 0.0;
} else if (value == P3.x) {
t = 1.0;
} else {
// Calculate t
double a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x;
double b = 3 * P0.x - 6 * P1.x + 3 * P2.x;
double c = -3 * P0.x + 3 * P1.x;
double d = P0.x - value;
double tTemp = SolveCubic(a, b, c, d);
if (tTemp == -1.0) {
return -1.0;
}
t = tTemp;
}
// Calculate y from t
double oneMinusT = 1.0 - t;
return (oneMinusT * oneMinusT * oneMinusT) * P0.y + 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t) * P3.y;
}
struct InterpolationPoint2D {
InterpolationPoint2D(Vector2D const point_, double distance_) :
point(point_), distance(distance_) {
}
Vector2D point;
double distance;
};
namespace {
double interpolateDouble(double value, double to, double amount) {
return value + ((to - value) * amount);
}
}
Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const {
auto a = interpolate(outTangent, amount);
auto b = outTangent.interpolate(inTangent, amount);
auto c = inTangent.interpolate(to, amount);
auto d = a.interpolate(b, amount);
auto e = b.interpolate(c, amount);
auto f = d.interpolate(e, amount);
return f;
}
Vector2D Vector2D::interpolate(Vector2D const &to, double amount) const {
return Vector2D(
interpolateDouble(x, to.x, amount),
interpolateDouble(y, to.y, amount)
);
}
Vector2D Vector2D::interpolate(
Vector2D const &to,
Vector2D const &outTangent,
Vector2D const &inTangent,
double amount,
int maxIterations,
int samples,
double accuracy
) const {
if (amount == 0.0) {
return *this;
}
if (amount == 1.0) {
return to;
}
if (colinear(outTangent, inTangent) && outTangent.colinear(inTangent, to)) {
return interpolate(to, amount);
}
double step = 1.0 / (double)samples;
std::vector<InterpolationPoint2D> points;
points.push_back(InterpolationPoint2D(*this, 0.0));
double totalLength = 0.0;
Vector2D previousPoint = *this;
double previousAmount = 0.0;
int closestPoint = 0;
while (previousAmount < 1.0) {
previousAmount = previousAmount + step;
if (previousAmount < amount) {
closestPoint = closestPoint + 1;
}
auto newPoint = pointOnPath(to, outTangent, inTangent, previousAmount);
auto distance = previousPoint.distanceTo(newPoint);
totalLength = totalLength + distance;
points.push_back(InterpolationPoint2D(newPoint, totalLength));
previousPoint = newPoint;
}
double accurateDistance = amount * totalLength;
auto point = points[closestPoint];
bool foundPoint = false;
double pointAmount = ((double)closestPoint) * step;
double nextPointAmount = pointAmount + step;
int refineIterations = 0;
while (!foundPoint) {
refineIterations = refineIterations + 1;
/// First see if the next point is still less than the projected length.
auto nextPoint = points[closestPoint + 1];
if (nextPoint.distance < accurateDistance) {
point = nextPoint;
closestPoint = closestPoint + 1;
pointAmount = ((double)closestPoint) * step;
nextPointAmount = pointAmount + step;
if (closestPoint == (int)points.size()) {
foundPoint = true;
}
continue;
}
if (accurateDistance < point.distance) {
closestPoint = closestPoint - 1;
if (closestPoint < 0) {
foundPoint = true;
continue;
}
point = points[closestPoint];
pointAmount = ((double)closestPoint) * step;
nextPointAmount = pointAmount + step;
continue;
}
/// Now we are certain the point is the closest point under the distance
auto pointDiff = nextPoint.distance - point.distance;
auto proposedPointAmount = remapDouble((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount);
auto newPoint = pointOnPath(to, outTangent, inTangent, proposedPointAmount);
auto newDistance = point.distance + point.point.distanceTo(newPoint);
pointAmount = proposedPointAmount;
point = InterpolationPoint2D(newPoint, newDistance);
if (accurateDistance - newDistance <= accuracy ||
newDistance - accurateDistance <= accuracy) {
foundPoint = true;
}
if (refineIterations == maxIterations) {
foundPoint = true;
}
}
return point.point;
}
::CATransform3D nativeTransform(CATransform3D const &value) {
::CATransform3D result;
result.m11 = value.m11;
result.m12 = value.m12;
result.m13 = value.m13;
result.m14 = value.m14;
result.m21 = value.m21;
result.m22 = value.m22;
result.m23 = value.m23;
result.m24 = value.m24;
result.m31 = value.m31;
result.m32 = value.m32;
result.m33 = value.m33;
result.m34 = value.m34;
result.m41 = value.m41;
result.m42 = value.m42;
result.m43 = value.m43;
result.m44 = value.m44;
return result;
}
CATransform3D fromNativeTransform(::CATransform3D const &value) {
CATransform3D result = CATransform3D::identity();
result.m11 = value.m11;
result.m12 = value.m12;
result.m13 = value.m13;
result.m14 = value.m14;
result.m21 = value.m21;
result.m22 = value.m22;
result.m23 = value.m23;
result.m24 = value.m24;
result.m31 = value.m31;
result.m32 = value.m32;
result.m33 = value.m33;
result.m34 = value.m34;
result.m41 = value.m41;
result.m42 = value.m42;
result.m43 = value.m43;
result.m44 = value.m44;
return result;
}
CATransform3D CATransform3D::makeRotation(double radians, double x, double y, double z) {
return fromNativeTransform(CATransform3DMakeRotation(radians, x, y, z));
/*if (x == 0.0 && y == 0.0 && z == 0.0) {
return CATransform3D::identity();
}
float s = sin(radians);
float c = cos(radians);
float len = sqrt(x*x + y*y + z*z);
x /= len; y /= len; z /= len;
CATransform3D returnValue = CATransform3D::identity();
returnValue.m11 = c + (1-c) * x*x;
returnValue.m12 = (1-c) * x*y + s*z;
returnValue.m13 = (1-c) * x*z - s*y;
returnValue.m14 = 0;
returnValue.m21 = (1-c) * y*x - s*z;
returnValue.m22 = c + (1-c) * y*y;
returnValue.m23 = (1-c) * y*z + s*x;
returnValue.m24 = 0;
returnValue.m31 = (1-c) * z*x + s*y;
returnValue.m32 = (1-c) * y*z - s*x;
returnValue.m33 = c + (1-c) * z*z;
returnValue.m34 = 0;
returnValue.m41 = 0;
returnValue.m42 = 0;
returnValue.m43 = 0;
returnValue.m44 = 1;
return returnValue;*/
}
CATransform3D CATransform3D::rotated(double degrees) const {
return fromNativeTransform(CATransform3DRotate(nativeTransform(*this), degreesToRadians(degrees), 0.0, 0.0, 1.0));
//return CATransform3D::makeRotation(degreesToRadians(degrees), 0.0, 0.0, 1.0) * (*this);
}
CATransform3D CATransform3D::translated(Vector2D const &translation) const {
return fromNativeTransform(CATransform3DTranslate(nativeTransform(*this), translation.x, translation.y, 0.0));
}
CATransform3D CATransform3D::scaled(Vector2D const &scale) const {
return fromNativeTransform(CATransform3DScale(nativeTransform(*this), scale.x, scale.y, 1.0));
//return CATransform3D::makeScale(scale.x, scale.y, 1.0) * (*this);
}
CATransform3D CATransform3D::operator*(CATransform3D const &b) const {
if (isIdentity()) {
return b;
}
if (b.isIdentity()) {
return *this;
}
const CATransform3D lhs = b;
const CATransform3D &rhs = *this;
CATransform3D result = CATransform3D::identity();
result.m11 = (lhs.m11*rhs.m11)+(lhs.m21*rhs.m12)+(lhs.m31*rhs.m13)+(lhs.m41*rhs.m14);
result.m12 = (lhs.m12*rhs.m11)+(lhs.m22*rhs.m12)+(lhs.m32*rhs.m13)+(lhs.m42*rhs.m14);
result.m13 = (lhs.m13*rhs.m11)+(lhs.m23*rhs.m12)+(lhs.m33*rhs.m13)+(lhs.m43*rhs.m14);
result.m14 = (lhs.m14*rhs.m11)+(lhs.m24*rhs.m12)+(lhs.m34*rhs.m13)+(lhs.m44*rhs.m14);
result.m21 = (lhs.m11*rhs.m21)+(lhs.m21*rhs.m22)+(lhs.m31*rhs.m23)+(lhs.m41*rhs.m24);
result.m22 = (lhs.m12*rhs.m21)+(lhs.m22*rhs.m22)+(lhs.m32*rhs.m23)+(lhs.m42*rhs.m24);
result.m23 = (lhs.m13*rhs.m21)+(lhs.m23*rhs.m22)+(lhs.m33*rhs.m23)+(lhs.m43*rhs.m24);
result.m24 = (lhs.m14*rhs.m21)+(lhs.m24*rhs.m22)+(lhs.m34*rhs.m23)+(lhs.m44*rhs.m24);
result.m31 = (lhs.m11*rhs.m31)+(lhs.m21*rhs.m32)+(lhs.m31*rhs.m33)+(lhs.m41*rhs.m34);
result.m32 = (lhs.m12*rhs.m31)+(lhs.m22*rhs.m32)+(lhs.m32*rhs.m33)+(lhs.m42*rhs.m34);
result.m33 = (lhs.m13*rhs.m31)+(lhs.m23*rhs.m32)+(lhs.m33*rhs.m33)+(lhs.m43*rhs.m34);
result.m34 = (lhs.m14*rhs.m31)+(lhs.m24*rhs.m32)+(lhs.m34*rhs.m33)+(lhs.m44*rhs.m34);
result.m41 = (lhs.m11*rhs.m41)+(lhs.m21*rhs.m42)+(lhs.m31*rhs.m43)+(lhs.m41*rhs.m44);
result.m42 = (lhs.m12*rhs.m41)+(lhs.m22*rhs.m42)+(lhs.m32*rhs.m43)+(lhs.m42*rhs.m44);
result.m43 = (lhs.m13*rhs.m41)+(lhs.m23*rhs.m42)+(lhs.m33*rhs.m43)+(lhs.m43*rhs.m44);
result.m44 = (lhs.m14*rhs.m41)+(lhs.m24*rhs.m42)+(lhs.m34*rhs.m43)+(lhs.m44*rhs.m44);
return result;
}
bool CATransform3D::isInvertible() const {
return std::abs(m11 * m22 - m12 * m21) >= 0.00000001;
}
CATransform3D CATransform3D::inverted() const {
return fromNativeTransform(CATransform3DMakeAffineTransform(CGAffineTransformInvert(CATransform3DGetAffineTransform(nativeTransform(*this)))));
}
bool CGRect::intersects(CGRect const &other) const {
return CGRectIntersectsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height));
}
bool CGRect::contains(CGRect const &other) const {
return CGRectContainsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height));
}
CGRect CGRect::intersection(CGRect const &other) const {
auto result = CGRectIntersection(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height));
return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height);
}
CGRect CGRect::unionWith(CGRect const &other) const {
auto result = CGRectUnion(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height));
return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height);
}
static inline Vector2D applyingTransformToPoint(CATransform3D const &transform, Vector2D const &point) {
double newX = point.x * transform.m11 + point.y * transform.m21 + transform.m41;
double newY = point.x * transform.m12 + point.y * transform.m22 + transform.m42;
double newW = point.x * transform.m14 + point.y * transform.m24 + transform.m44;
return Vector2D(newX / newW, newY / newW);
}
CGRect CGRect::applyingTransform(CATransform3D const &transform) const {
if (transform.isIdentity()) {
return *this;
}
Vector2D topLeft = applyingTransformToPoint(transform, Vector2D(x, y));
Vector2D topRight = applyingTransformToPoint(transform, Vector2D(x + width, y));
Vector2D bottomLeft = applyingTransformToPoint(transform, Vector2D(x, y + height));
Vector2D bottomRight = applyingTransformToPoint(transform, Vector2D(x + width, y + height));
double minX = topLeft.x;
if (topRight.x < minX) {
minX = topRight.x;
}
if (bottomLeft.x < minX) {
minX = bottomLeft.x;
}
if (bottomRight.x < minX) {
minX = bottomRight.x;
}
double minY = topLeft.y;
if (topRight.y < minY) {
minY = topRight.y;
}
if (bottomLeft.y < minY) {
minY = bottomLeft.y;
}
if (bottomRight.y < minY) {
minY = bottomRight.y;
}
double maxX = topLeft.x;
if (topRight.x > maxX) {
maxX = topRight.x;
}
if (bottomLeft.x > maxX) {
maxX = bottomLeft.x;
}
if (bottomRight.x > maxX) {
maxX = bottomRight.x;
}
double maxY = topLeft.y;
if (topRight.y > maxY) {
maxY = topRight.y;
}
if (bottomLeft.y > maxY) {
maxY = bottomLeft.y;
}
if (bottomRight.y > maxY) {
maxY = bottomRight.y;
}
CGRect result(minX, minY, maxX - minX, maxY - minY);
return result;
}
}