From 571fb8f0331b5976a32214e42b26122903c4f740 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 20 Nov 2021 17:20:37 +0400 Subject: [PATCH 1/2] Optimize bezier tesselation --- Tests/LottieMesh/Sources/ViewController.swift | 10 ++- .../LottieMeshBinding/LottieMeshBinding.h | 2 +- .../Sources/LottieMeshBinding.mm | 78 ++++++++++++------- .../Sources/MeshAnimation.swift | 31 +++----- 4 files changed, 71 insertions(+), 50 deletions(-) diff --git a/Tests/LottieMesh/Sources/ViewController.swift b/Tests/LottieMesh/Sources/ViewController.swift index 40d86d5d76..b69daa6e7d 100644 --- a/Tests/LottieMesh/Sources/ViewController.swift +++ b/Tests/LottieMesh/Sources/ViewController.swift @@ -2,11 +2,14 @@ import Foundation import UIKit import LottieMeshSwift +import Postbox public final class ViewController: UIViewController { override public func viewDidLoad() { super.viewDidLoad() + TempBox.initializeShared(basePath: NSTemporaryDirectory(), processType: "test", launchSpecificId: Int64.random(in: Int64.min ..< Int64.max)) + self.view.backgroundColor = .black let path = Bundle.main.path(forResource: "Fireworks", ofType: "json")! @@ -16,7 +19,12 @@ public final class ViewController: UIViewController { }*/ if #available(iOS 13.0, *) { - let animation = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))! + let startTime = CFAbsoluteTimeGetCurrent() + let animationFile = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))! + print("Time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0)") + let buffer = MeshReadBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: animationFile.path))) + let animation = MeshAnimation.read(buffer: buffer) + let renderer = MeshRenderer(wireframe: true)! renderer.frame = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: CGSize(width: 300.0, height: 300.0)) diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h b/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h index 8b2a1b15e4..18d6073a22 100644 --- a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h +++ b/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h @@ -34,7 +34,7 @@ typedef NS_CLOSED_ENUM(NSInteger, LottieMeshFillRule) { - (void)getVertexAt:(NSInteger)index x:(float * _Nullable)x y:(float * _Nullable)y; - (NSInteger)triangleCount; -- (void)getTriangleAt:(NSInteger)index v0:(NSInteger * _Nullable)v0 v1:(NSInteger * _Nullable)v1 v2:(NSInteger * _Nullable)v2; +- (void * _Nonnull)getTriangles; + (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill:(LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke; diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm b/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm index e76e7d974c..75a24b6bcd 100644 --- a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm +++ b/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm @@ -10,12 +10,6 @@ MeshGenerator::Point bezierQuadraticPointAt(MeshGenerator::Point const &p0, Mesh return MeshGenerator::Point(x, y); } -MeshGenerator::Point bezierQubicPointAt(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, MeshGenerator::Point const &p3, float t) { - float x = powf((1.0 - t), 3.0) * p0.x + 3.0 * powf((1.0 - t), 2.0) * t * p1.x + 3.0 * (1.0 - t) * powf(t, 2.0) * p2.x + powf(t, 3.0) * p3.x; - float y = powf((1.0 - t), 3.0) * p0.y + 3.0 * powf((1.0 - t), 2.0) * t * p1.y + 3.0 * (1.0 - t) * powf(t, 2.0) * p2.y + powf(t, 3.0) * p3.y; - return MeshGenerator::Point(x, y); -} - float approximateBezierQuadraticLength(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2) { float length = 0.0f; float t = 0.1; @@ -29,17 +23,51 @@ float approximateBezierQuadraticLength(MeshGenerator::Point const &p0, MeshGener return length; } -float approximateBezierQubicLength(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, MeshGenerator::Point const &p3) { - float length = 0.0f; - float t = 0.1; - MeshGenerator::Point last = p0; - while (t < 1.01) { - auto point = bezierQubicPointAt(p0, p1, p2, p3, t); - length += last.distance(point); - last = point; - t += 0.1; +void tesselateBezier(MeshGenerator::Path &path, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, MeshGenerator::Point const &p3, MeshGenerator::Point const &p4, int level) { + const float tessTol = 0.25f / 0.5f; + + float x1 = p1.x; + float y1 = p1.y; + float x2 = p2.x; + float y2 = p2.y; + float x3 = p3.x; + float y3 = p3.y; + float x4 = p4.x; + float y4 = p4.y; + + float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234; + float dx, dy, d2, d3; + + if (level > 10) { + return; } - return length; + + x12 = (x1 + x2) * 0.5f; + y12 = (y1 + y2) * 0.5f; + x23 = (x2 + x3) * 0.5f; + y23 = (y2 + y3) * 0.5f; + x34 = (x3 + x4) * 0.5f; + y34 = (y3 + y4) * 0.5f; + x123 = (x12 + x23) * 0.5f; + y123 = (y12 + y23) * 0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = std::abs(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = std::abs(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3) * (d2 + d3) < tessTol * (dx * dx + dy * dy)) { + path.points.emplace_back(x4, y4); + return; + } + + x234 = (x23+x34) * 0.5f; + y234 = (y23+y34) * 0.5f; + x1234 = (x123 + x234) * 0.5f; + y1234 = (y123 + y234) * 0.5f; + + tesselateBezier(path, MeshGenerator::Point(x1, y1), MeshGenerator::Point(x12, y12), MeshGenerator::Point(x123, y123), MeshGenerator::Point(x1234, y1234), level + 1); + tesselateBezier(path, MeshGenerator::Point(x1234, y1234), MeshGenerator::Point(x234, y234), MeshGenerator::Point(x34, y34), MeshGenerator::Point(x4, y4), level + 1); } } @@ -80,7 +108,11 @@ float approximateBezierQubicLength(MeshGenerator::Point const &p0, MeshGenerator return (NSInteger)(_mesh->triangles.size() / 3); } -- (void)getTriangleAt:(NSInteger)index v0:(NSInteger * _Nullable)v0 v1:(NSInteger * _Nullable)v1 v2:(NSInteger * _Nullable)v2 { +- (void * _Nonnull)getTriangles { + return _mesh->triangles.data(); +} + +/*- (void)getTriangleAt:(NSInteger)index v0:(NSInteger * _Nullable)v0 v1:(NSInteger * _Nullable)v1 v2:(NSInteger * _Nullable)v2 { if (v0) { *v0 = (NSInteger)_mesh->triangles[index * 3 + 0]; } @@ -90,7 +122,7 @@ float approximateBezierQubicLength(MeshGenerator::Point const &p0, MeshGenerator if (v2) { *v2 = (NSInteger)_mesh->triangles[index * 3 + 2]; } -} +}*/ + (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill: (LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke { float scale = 1.0f; @@ -173,14 +205,8 @@ float approximateBezierQubicLength(MeshGenerator::Point const &p0, MeshGenerator MeshGenerator::Point p1(element->points[0].x * scale, element->points[0].y * scale); MeshGenerator::Point p2(element->points[1].x * scale, element->points[1].y * scale); MeshGenerator::Point p3(element->points[2].x * scale, element->points[2].y * scale); - - float step = 10.0f * flatness / approximateBezierQubicLength(p0, p1, p2, p3); - while (t < 1.0f) { - auto point = bezierQubicPointAt(p0, p1, p2, p3, t); - paths[paths.size() - 1].points.push_back(point); - t += step; - } - paths[paths.size() - 1].points.push_back(p3); + + tesselateBezier(paths[paths.size() - 1], p0, p1, p2, p3, 0); } break; } diff --git a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift b/submodules/LottieMeshSwift/Sources/MeshAnimation.swift index a7710444df..b145a96a6d 100644 --- a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift +++ b/submodules/LottieMeshSwift/Sources/MeshAnimation.swift @@ -573,13 +573,13 @@ public final class MeshRenderer: MTKView { } } -private func generateSegments(geometry: CapturedGeometryNode, superAlpha: CGFloat, superTransform: CGAffineTransform, writeSegment: (MeshAnimation.Frame.Segment) -> Void) { +private func generateSegments(writeBuffer: MeshWriteBuffer, segmentCount: inout Int, geometry: CapturedGeometryNode, superAlpha: CGFloat, superTransform: CGAffineTransform) { if geometry.isHidden || geometry.alpha.isZero { return } for i in 0 ..< geometry.subnodes.count { - generateSegments(geometry: geometry.subnodes[i], superAlpha: superAlpha * geometry.alpha, superTransform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform), writeSegment: writeSegment) + generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry.subnodes[i], superAlpha: superAlpha * geometry.alpha, superTransform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) } if let displayItem = geometry.displayItem { @@ -617,17 +617,6 @@ private func generateSegments(geometry: CapturedGeometryNode, superAlpha: CGFloa meshData = LottieMeshData.generate(with: UIBezierPath(cgPath: displayItem.path), fill: nil, stroke: LottieMeshStroke(lineWidth: stroke.lineWidth, lineJoin: stroke.lineJoin, lineCap: stroke.lineCap, miterLimit: stroke.miterLimit)) } if let meshData = meshData, meshData.triangleCount() != 0 { - let mappedTriangles = WriteBuffer() - for i in 0 ..< meshData.triangleCount() { - var v0: Int = 0 - var v1: Int = 0 - var v2: Int = 0 - meshData.getTriangleAt(i, v0: &v0, v1: &v1, v2: &v2) - mappedTriangles.writeInt32(Int32(v0)) - mappedTriangles.writeInt32(Int32(v1)) - mappedTriangles.writeInt32(Int32(v2)) - } - let mappedVertices = WriteBuffer() for i in 0 ..< meshData.vertexCount() { var x: Float = 0.0 @@ -636,13 +625,15 @@ private func generateSegments(geometry: CapturedGeometryNode, superAlpha: CGFloa mappedVertices.writeFloat(x) mappedVertices.writeFloat(y) } + + let trianglesData = Data(bytes: meshData.getTriangles(), count: meshData.triangleCount() * 3 * 4) let verticesData = mappedVertices.makeData() - let trianglesData = mappedTriangles.makeData() let segment = MeshAnimation.Frame.Segment(vertices: DataRange(data: verticesData, range: 0 ..< verticesData.count), triangles: DataRange(data: trianglesData, range: 0 ..< trianglesData.count), fill: triangleFill, transform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) - writeSegment(segment) + segment.write(buffer: writeBuffer) + segmentCount += 1 } } } @@ -666,9 +657,9 @@ public func generateMeshAnimation(data: Data) -> TempBoxFile? { for i in 0 ..< Int(animation.endFrame) { container.setFrame(frame: CGFloat(i)) - #if DEBUG + //#if DEBUG print("Frame \(i) / \(Int(animation.endFrame))") - #endif + //#endif let segmentCountOffset = writeBuffer.offset writeBuffer.writeInt32(0) @@ -677,11 +668,7 @@ public func generateMeshAnimation(data: Data) -> TempBoxFile? { let geometry = container.captureGeometry() geometry.transform = CATransform3DMakeTranslation(256.0, 256.0, 0.0) - generateSegments(geometry: geometry, superAlpha: 1.0, superTransform: .identity, writeSegment: { segment in - segment.write(buffer: writeBuffer) - - segmentCount += 1 - }) + generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry, superAlpha: 1.0, superTransform: .identity) let currentOffset = writeBuffer.offset writeBuffer.seek(offset: segmentCountOffset) From 68986f9e5bd39b0de09538d64df2762d2402baaa Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 20 Nov 2021 17:58:30 +0400 Subject: [PATCH 2/2] Fix spring animation --- submodules/ContextUI/Sources/ContextController.swift | 9 +++++---- submodules/Display/Source/CAAnimationUtils.swift | 4 ++-- .../Sources/ReactionContextNode.swift | 9 +++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index ea74e730f7..ec062da935 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1135,6 +1135,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } self.reactionContextNodeIsAnimatingOut = true + reactionContextNode.willAnimateOutToReaction(value: value) + self.animateOut(result: .default, completion: { + contentCompleted = true + intermediateCompletion() + }) reactionContextNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in guard let strongSelf = self else { return @@ -1144,10 +1149,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi reactionCompleted = true intermediateCompletion() }) - self.animateOut(result: .default, completion: { - contentCompleted = true - intermediateCompletion() - }) self.isUserInteractionEnabled = false } diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 9c51d016d1..5fc052945d 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -151,7 +151,7 @@ public extension CALayer { let animationGroup = CAAnimationGroup() var timeOffset = 0.0 for animation in animations { - animation.beginTime = animation.beginTime + timeOffset + animation.beginTime = self.convertTime(animation.beginTime, from: nil) + timeOffset timeOffset += animation.duration / Double(animation.speed) } animationGroup.animations = animations @@ -217,7 +217,7 @@ public extension CALayer { } if !delay.isZero { - animation.beginTime = CACurrentMediaTime() + delay * UIView.animationDurationFactor() + animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor() animation.fillMode = .both } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 9f59509492..2b2a94536f 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -528,6 +528,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / itemNode.bounds.width, duration: duration, removeOnCompletion: false) } + public func willAnimateOutToReaction(value: String) { + for itemNode in self.itemNodes { + if itemNode.item.reaction.rawValue != value { + continue + } + itemNode.isExtracted = true + } + } + public func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) { for itemNode in self.itemNodes { if itemNode.item.reaction.rawValue != value {