Optimize bezier tesselation

This commit is contained in:
Ali 2021-11-20 17:20:37 +04:00
parent 770afde804
commit 571fb8f033
4 changed files with 71 additions and 50 deletions

View File

@ -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))

View File

@ -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;

View File

@ -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;
}

View File

@ -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)