Skip to content

Commit efe1f0b

Browse files
authoredJul 2, 2021
Merge pull request #4 from pocketpixels/patch-1
Support animation durations shorter than 1 second Thanks for the edits @pocketpixels!
2 parents f223ebf + b9c6a4d commit efe1f0b

File tree

4 files changed

+100
-23
lines changed

4 files changed

+100
-23
lines changed
 

‎.github/workflows/swift-build.yml

+10-16
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,18 @@ name: build
22

33
on:
44
push:
5-
branches:
6-
- "master"
7-
tags:
8-
- "!*"
5+
branches: [ master ]
96
pull_request:
10-
branches:
11-
- "*"
7+
branches: [ master ]
128

139
jobs:
1410
build:
15-
runs-on: macOS-latest
11+
12+
runs-on: macos-latest
13+
1614
steps:
17-
- uses: actions/checkout@v1
18-
- name: Build Package
19-
run: |
20-
swift package generate-xcodeproj
21-
xcodebuild clean build -project $PROJECT -scheme $SCHEME -destination "$DESTINATION" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
22-
env:
23-
PROJECT: SCNBezier.xcodeproj
24-
SCHEME: SCNBezier-Package
25-
DESTINATION: platform=iOS Simulator,name=iPhone Xs
15+
- uses: actions/checkout@v2
16+
- name: Build
17+
run: swift build -v
18+
- name: Run tests
19+
run: swift test -v

‎Package.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import PackageDescription
55

66
let package = Package(
77
name: "SCNBezier",
8-
platforms: [.iOS(.v8), .macOS(.v10_10), .tvOS(.v9), .watchOS(.v3)],
8+
platforms: [.iOS(.v9), .macOS(.v10_10), .tvOS(.v9), .watchOS(.v3)],
99
products: [.library(name: "SCNBezier", targets: ["SCNBezier"])],
10-
targets: [.target(name: "SCNBezier")],
10+
targets: [
11+
.target(name: "SCNBezier"),
12+
.testTarget(
13+
name: "SCNBezierTests",
14+
dependencies: ["SCNBezier"])
15+
],
1116
swiftLanguageVersions: [.v5]
1217
)

‎Sources/SCNBezier/SCNAction+Extensions.swift

+26-5
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,38 @@ public extension SCNAction {
1717
/// - fps: how frequent the position should be updated (default 30)
1818
/// - interpolator: time interpolator for easing
1919
/// - Returns: SCNAction to be applied to a node
20-
class func moveAlong(
20+
static func moveAlong(
2121
path: SCNBezierPath, duration: TimeInterval, fps: Int = 30,
2222
interpolator: ((TimeInterval) -> TimeInterval)? = nil
2323
) -> SCNAction {
24-
let actions = path.getNPoints(count: Int(duration) * fps, interpolator: interpolator).map { (point) -> SCNAction in
25-
let tInt = 1 / TimeInterval(fps)
26-
return SCNAction.move(to: point, duration: tInt)
27-
}
24+
let actions = SCNAction.getActions(
25+
path: path, duration: duration, fps: fps,
26+
interpolator: interpolator
27+
)
2828
return SCNAction.sequence(actions)
2929
}
3030

31+
internal static func getActions(
32+
path: SCNBezierPath, duration: TimeInterval, fps: Int = 30,
33+
interpolator: ((TimeInterval) -> TimeInterval)? = nil
34+
) -> [SCNAction] {
35+
let nPoints = path.getNPoints(
36+
count: max(2, Int(ceil(duration * Double(fps)))), interpolator: interpolator
37+
)
38+
let actions = nPoints.enumerated().map { (iterator) -> SCNAction in
39+
if iterator.offset == 0 {
40+
// The first action should be instant, making sure the
41+
// SCNNode is in the starting position
42+
return SCNAction.move(to: iterator.element, duration: 0)
43+
}
44+
// The duration of each actuion should be a fraction of the full duration
45+
// n points, n - 1 moving actions, so duration / (n - 1)
46+
let tInt = duration / Double(nPoints.count - 1)
47+
return SCNAction.move(to: iterator.element, duration: tInt)
48+
}
49+
return actions
50+
}
51+
3152
/// Move along a Bezier Path represented by a list of SCNVector3
3253
///
3354
/// - Parameters:
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
import XCTest
3+
@testable import SCNBezier
4+
import SceneKit
5+
6+
internal func - (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
7+
return SCNVector3Make(left.x - right.x, left.y - right.y, left.z - right.z)
8+
}
9+
internal func + (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
10+
return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
11+
}
12+
internal func * (left: SCNVector3, right: VectorVal) -> SCNVector3 {
13+
return SCNVector3Make(left.x * right, left.y * right, left.z * right)
14+
}
15+
16+
internal extension SCNVector3 {
17+
var length_squared: Float {
18+
Float(sqrt(x * x + y * y + z * z))
19+
}
20+
}
21+
22+
final class SCNBezierTests: XCTestCase {
23+
func testBasicBezier() throws {
24+
let bezPositions = [
25+
SCNVector3(-1, 1, 0.01),
26+
SCNVector3(1, 0.5, 0.4),
27+
SCNVector3(1.0, -1, 0.1),
28+
SCNVector3(0.4, -0.5, 0.01)
29+
]
30+
31+
let points = SCNBezierPath(points: bezPositions).getNPoints(count: 100)
32+
XCTAssertTrue(points.count == 100, "Wrong number of points: \(bezPositions.count)")
33+
checkPositionsEqual(bezPositions.first!, points.first!)
34+
checkPositionsEqual(bezPositions.last!, points.last!)
35+
}
36+
37+
func testUnevenValue() throws {
38+
let bezPositions = [
39+
SCNVector3(-1, 1, 0.01),
40+
SCNVector3(1, 0.5, 0.4),
41+
SCNVector3(1.0, -1, 0.1),
42+
SCNVector3(0.4, -0.5, 0.01)
43+
]
44+
let bezPath = SCNBezierPath(points: bezPositions)
45+
let actions = SCNAction.getActions(path: bezPath, duration: 0.3, fps: 1)
46+
let actionSequence = SCNAction.sequence(actions)
47+
XCTAssertTrue(actions.count == 2, "should have at least 2 actions!")
48+
XCTAssertTrue(actionSequence.duration == 0.3, "Action sequence wrong length: \(actionSequence.duration)")
49+
XCTAssertTrue(actions.first!.duration == 0, "Action sequence wrong length: \(actions.first!.duration)")
50+
XCTAssertTrue(actions.last!.duration == 0.3, "Action sequence wrong length: \(actions.last!.duration)")
51+
}
52+
53+
func checkPositionsEqual(_ first: SCNVector3, _ second: SCNVector3, prependMessage: String = "") {
54+
let endDiff = (first - second).length_squared
55+
XCTAssertTrue(endDiff < 1e-5, "\(prependMessage)\nLast point is not correct \(first) vs \(second)")
56+
}
57+
}

0 commit comments

Comments
 (0)