@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +14 -12
- package/android/CORE.md +9 -6
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +2 -0
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +21 -0
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +14 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +24 -20
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +12 -7
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/options/animations.ts +12 -7
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#import <QuartzCore/QuartzCore.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/// A 4×4 transform split into the parts it is made of (the decomposition of CSS Transforms Level 2): applied to a point
|
|
7
|
+
/// in the order scale, skew, rotation, translation, perspective. Two transforms interpolate part by part, the rotation
|
|
8
|
+
/// along the shorter arc between the two (a quaternion's slerp), so a rotation never collapses on the way.
|
|
9
|
+
typedef struct {
|
|
10
|
+
double translate[3];
|
|
11
|
+
double scale[3];
|
|
12
|
+
/// The shears xy, xz, yz.
|
|
13
|
+
double skew[3];
|
|
14
|
+
double perspective[4];
|
|
15
|
+
/// x, y, z, w.
|
|
16
|
+
double quaternion[4];
|
|
17
|
+
} ANNTransformParts;
|
|
18
|
+
|
|
19
|
+
/// The parts of no transform.
|
|
20
|
+
FOUNDATION_EXPORT ANNTransformParts ANNTransformPartsIdentity(void);
|
|
21
|
+
|
|
22
|
+
/// The parts of `transform` into `parts`; NO when it can't be split (a scale of 0 flattens it).
|
|
23
|
+
FOUNDATION_EXPORT BOOL ANNTransformPartsOf(CATransform3D transform, ANNTransformParts *parts);
|
|
24
|
+
|
|
25
|
+
/// The transform the parts make.
|
|
26
|
+
FOUNDATION_EXPORT CATransform3D ANNTransformOfParts(ANNTransformParts parts);
|
|
27
|
+
|
|
28
|
+
/// The parts at `f` of the way: each number from + f·(to − from), the rotation by slerp along the shorter arc; exactly
|
|
29
|
+
/// `from` at f = 0 and `to` at f = 1. Two rotations half a turn apart go either way.
|
|
30
|
+
FOUNDATION_EXPORT ANNTransformParts ANNInterpolateTransformParts(ANNTransformParts from,
|
|
31
|
+
ANNTransformParts to, double f);
|
|
32
|
+
|
|
33
|
+
/// The rotation of the parts as the turns about x, then y, then z (degrees, each in −180…180), the order a view's
|
|
34
|
+
/// tracks compose them in (`ANNViewTracks`).
|
|
35
|
+
FOUNDATION_EXPORT void ANNRotationDegreesOf(ANNTransformParts parts, double *x, double *y,
|
|
36
|
+
double *z);
|
|
37
|
+
|
|
38
|
+
/// The shears of the parts as a transform of their own (the scale, rotation and the rest aside).
|
|
39
|
+
FOUNDATION_EXPORT CATransform3D ANNSkewTransformOf(ANNTransformParts parts);
|
|
40
|
+
|
|
41
|
+
#if DEBUG
|
|
42
|
+
/// Development only (there is no iOS test target): splits and rebuilds known transforms and checks the rotations a
|
|
43
|
+
/// shared element takes (0° → 90°, 0° → 180°, 45° → −45°, 270°, 170° → −170°, a scale, a tilt), once; a mismatch is
|
|
44
|
+
/// logged.
|
|
45
|
+
FOUNDATION_EXPORT void ANNTransformPartsSelfCheck(void);
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
#import "ANNTransformParts.h"
|
|
2
|
+
#import <React/RCTLog.h>
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
// The math works on m[i][j] = m(i+1)(j+1) of `CATransform3D`: a row is what a point's coordinate multiplies (row
|
|
7
|
+
// vectors), the layout of the decomposition's own pseudo-code (its matrix[3][0..2] is the translation).
|
|
8
|
+
typedef double ANNMatrix[4][4];
|
|
9
|
+
|
|
10
|
+
static void ANNMatrixOf(CATransform3D t, ANNMatrix m) {
|
|
11
|
+
const CGFloat values[16] = {t.m11, t.m12, t.m13, t.m14, t.m21, t.m22, t.m23, t.m24,
|
|
12
|
+
t.m31, t.m32, t.m33, t.m34, t.m41, t.m42, t.m43, t.m44};
|
|
13
|
+
for (int i = 0; i < 16; i++) {
|
|
14
|
+
m[i / 4][i % 4] = values[i];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static CATransform3D ANNTransformOfMatrix(const ANNMatrix m) {
|
|
19
|
+
CATransform3D t;
|
|
20
|
+
t.m11 = m[0][0];
|
|
21
|
+
t.m12 = m[0][1];
|
|
22
|
+
t.m13 = m[0][2];
|
|
23
|
+
t.m14 = m[0][3];
|
|
24
|
+
t.m21 = m[1][0];
|
|
25
|
+
t.m22 = m[1][1];
|
|
26
|
+
t.m23 = m[1][2];
|
|
27
|
+
t.m24 = m[1][3];
|
|
28
|
+
t.m31 = m[2][0];
|
|
29
|
+
t.m32 = m[2][1];
|
|
30
|
+
t.m33 = m[2][2];
|
|
31
|
+
t.m34 = m[2][3];
|
|
32
|
+
t.m41 = m[3][0];
|
|
33
|
+
t.m42 = m[3][1];
|
|
34
|
+
t.m43 = m[3][2];
|
|
35
|
+
t.m44 = m[3][3];
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static void ANNMatrixIdentity(ANNMatrix m) {
|
|
40
|
+
for (int i = 0; i < 4; i++) {
|
|
41
|
+
for (int j = 0; j < 4; j++) {
|
|
42
|
+
m[i][j] = i == j ? 1 : 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// a · b: the point goes through a, then through b.
|
|
48
|
+
static void ANNMatrixMultiply(const ANNMatrix a, const ANNMatrix b, ANNMatrix out) {
|
|
49
|
+
ANNMatrix product;
|
|
50
|
+
for (int i = 0; i < 4; i++) {
|
|
51
|
+
for (int j = 0; j < 4; j++) {
|
|
52
|
+
double sum = 0;
|
|
53
|
+
for (int k = 0; k < 4; k++) {
|
|
54
|
+
sum += a[i][k] * b[k][j];
|
|
55
|
+
}
|
|
56
|
+
product[i][j] = sum;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
memcpy(out, product, sizeof(ANNMatrix));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The inverse of m into `out`; NO when m has none.
|
|
63
|
+
static BOOL ANNMatrixInvert(const ANNMatrix m, ANNMatrix out) {
|
|
64
|
+
const double *a = &m[0][0];
|
|
65
|
+
double inv[16];
|
|
66
|
+
inv[0] = a[5] * a[10] * a[15] - a[5] * a[11] * a[14] - a[9] * a[6] * a[15] +
|
|
67
|
+
a[9] * a[7] * a[14] + a[13] * a[6] * a[11] - a[13] * a[7] * a[10];
|
|
68
|
+
inv[4] = -a[4] * a[10] * a[15] + a[4] * a[11] * a[14] + a[8] * a[6] * a[15] -
|
|
69
|
+
a[8] * a[7] * a[14] - a[12] * a[6] * a[11] + a[12] * a[7] * a[10];
|
|
70
|
+
inv[8] = a[4] * a[9] * a[15] - a[4] * a[11] * a[13] - a[8] * a[5] * a[15] +
|
|
71
|
+
a[8] * a[7] * a[13] + a[12] * a[5] * a[11] - a[12] * a[7] * a[9];
|
|
72
|
+
inv[12] = -a[4] * a[9] * a[14] + a[4] * a[10] * a[13] + a[8] * a[5] * a[14] -
|
|
73
|
+
a[8] * a[6] * a[13] - a[12] * a[5] * a[10] + a[12] * a[6] * a[9];
|
|
74
|
+
inv[1] = -a[1] * a[10] * a[15] + a[1] * a[11] * a[14] + a[9] * a[2] * a[15] -
|
|
75
|
+
a[9] * a[3] * a[14] - a[13] * a[2] * a[11] + a[13] * a[3] * a[10];
|
|
76
|
+
inv[5] = a[0] * a[10] * a[15] - a[0] * a[11] * a[14] - a[8] * a[2] * a[15] +
|
|
77
|
+
a[8] * a[3] * a[14] + a[12] * a[2] * a[11] - a[12] * a[3] * a[10];
|
|
78
|
+
inv[9] = -a[0] * a[9] * a[15] + a[0] * a[11] * a[13] + a[8] * a[1] * a[15] -
|
|
79
|
+
a[8] * a[3] * a[13] - a[12] * a[1] * a[11] + a[12] * a[3] * a[9];
|
|
80
|
+
inv[13] = a[0] * a[9] * a[14] - a[0] * a[10] * a[13] - a[8] * a[1] * a[14] +
|
|
81
|
+
a[8] * a[2] * a[13] + a[12] * a[1] * a[10] - a[12] * a[2] * a[9];
|
|
82
|
+
inv[2] = a[1] * a[6] * a[15] - a[1] * a[7] * a[14] - a[5] * a[2] * a[15] +
|
|
83
|
+
a[5] * a[3] * a[14] + a[13] * a[2] * a[7] - a[13] * a[3] * a[6];
|
|
84
|
+
inv[6] = -a[0] * a[6] * a[15] + a[0] * a[7] * a[14] + a[4] * a[2] * a[15] -
|
|
85
|
+
a[4] * a[3] * a[14] - a[12] * a[2] * a[7] + a[12] * a[3] * a[6];
|
|
86
|
+
inv[10] = a[0] * a[5] * a[15] - a[0] * a[7] * a[13] - a[4] * a[1] * a[15] +
|
|
87
|
+
a[4] * a[3] * a[13] + a[12] * a[1] * a[7] - a[12] * a[3] * a[5];
|
|
88
|
+
inv[14] = -a[0] * a[5] * a[14] + a[0] * a[6] * a[13] + a[4] * a[1] * a[14] -
|
|
89
|
+
a[4] * a[2] * a[13] - a[12] * a[1] * a[6] + a[12] * a[2] * a[5];
|
|
90
|
+
inv[3] = -a[1] * a[6] * a[11] + a[1] * a[7] * a[10] + a[5] * a[2] * a[11] -
|
|
91
|
+
a[5] * a[3] * a[10] - a[9] * a[2] * a[7] + a[9] * a[3] * a[6];
|
|
92
|
+
inv[7] = a[0] * a[6] * a[11] - a[0] * a[7] * a[10] - a[4] * a[2] * a[11] +
|
|
93
|
+
a[4] * a[3] * a[10] + a[8] * a[2] * a[7] - a[8] * a[3] * a[6];
|
|
94
|
+
inv[11] = -a[0] * a[5] * a[11] + a[0] * a[7] * a[9] + a[4] * a[1] * a[11] -
|
|
95
|
+
a[4] * a[3] * a[9] - a[8] * a[1] * a[7] + a[8] * a[3] * a[5];
|
|
96
|
+
inv[15] = a[0] * a[5] * a[10] - a[0] * a[6] * a[9] - a[4] * a[1] * a[10] +
|
|
97
|
+
a[4] * a[2] * a[9] + a[8] * a[1] * a[6] - a[8] * a[2] * a[5];
|
|
98
|
+
double determinant = a[0] * inv[0] + a[1] * inv[4] + a[2] * inv[8] + a[3] * inv[12];
|
|
99
|
+
if (determinant == 0 || !std::isfinite(determinant)) {
|
|
100
|
+
return NO;
|
|
101
|
+
}
|
|
102
|
+
for (int i = 0; i < 16; i++) {
|
|
103
|
+
out[i / 4][i % 4] = inv[i] / determinant;
|
|
104
|
+
}
|
|
105
|
+
return YES;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static double ANNLength(const double v[3]) {
|
|
109
|
+
return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static double ANNDot(const double a[3], const double b[3]) {
|
|
113
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// a·sa + b·sb into a.
|
|
117
|
+
static void ANNCombine(double a[3], const double b[3], double sa, double sb) {
|
|
118
|
+
for (int i = 0; i < 3; i++) {
|
|
119
|
+
a[i] = a[i] * sa + b[i] * sb;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ANNTransformParts ANNTransformPartsIdentity(void) {
|
|
124
|
+
ANNTransformParts parts = {};
|
|
125
|
+
parts.scale[0] = parts.scale[1] = parts.scale[2] = 1;
|
|
126
|
+
parts.perspective[3] = 1;
|
|
127
|
+
parts.quaternion[3] = 1;
|
|
128
|
+
return parts;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
BOOL ANNTransformPartsOf(CATransform3D transform, ANNTransformParts *parts) {
|
|
132
|
+
ANNMatrix m;
|
|
133
|
+
ANNMatrixOf(transform, m);
|
|
134
|
+
if (m[3][3] == 0) {
|
|
135
|
+
return NO;
|
|
136
|
+
}
|
|
137
|
+
double w = m[3][3];
|
|
138
|
+
for (int i = 0; i < 4; i++) {
|
|
139
|
+
for (int j = 0; j < 4; j++) {
|
|
140
|
+
m[i][j] /= w;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// The perspective's own matrix: the rest without the perspective column; it also tells a flat transform.
|
|
144
|
+
ANNMatrix perspectiveMatrix;
|
|
145
|
+
memcpy(perspectiveMatrix, m, sizeof(ANNMatrix));
|
|
146
|
+
for (int i = 0; i < 3; i++) {
|
|
147
|
+
perspectiveMatrix[i][3] = 0;
|
|
148
|
+
}
|
|
149
|
+
perspectiveMatrix[3][3] = 1;
|
|
150
|
+
ANNMatrix inverse;
|
|
151
|
+
if (!ANNMatrixInvert(perspectiveMatrix, inverse)) {
|
|
152
|
+
return NO;
|
|
153
|
+
}
|
|
154
|
+
ANNTransformParts result = {};
|
|
155
|
+
if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0) {
|
|
156
|
+
const double right[4] = {m[0][3], m[1][3], m[2][3], m[3][3]};
|
|
157
|
+
// right · (inverse transposed): each component is a row of the inverse dotted with `right`.
|
|
158
|
+
for (int i = 0; i < 4; i++) {
|
|
159
|
+
double sum = 0;
|
|
160
|
+
for (int j = 0; j < 4; j++) {
|
|
161
|
+
sum += right[j] * inverse[i][j];
|
|
162
|
+
}
|
|
163
|
+
result.perspective[i] = sum;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
result.perspective[3] = 1;
|
|
167
|
+
}
|
|
168
|
+
for (int i = 0; i < 3; i++) {
|
|
169
|
+
result.translate[i] = m[3][i];
|
|
170
|
+
}
|
|
171
|
+
double row[3][3];
|
|
172
|
+
for (int i = 0; i < 3; i++) {
|
|
173
|
+
for (int j = 0; j < 3; j++) {
|
|
174
|
+
row[i][j] = m[i][j];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
result.scale[0] = ANNLength(row[0]);
|
|
178
|
+
if (result.scale[0] == 0) {
|
|
179
|
+
return NO;
|
|
180
|
+
}
|
|
181
|
+
ANNCombine(row[0], row[0], 1 / result.scale[0], 0);
|
|
182
|
+
result.skew[0] = ANNDot(row[0], row[1]);
|
|
183
|
+
ANNCombine(row[1], row[0], 1, -result.skew[0]);
|
|
184
|
+
result.scale[1] = ANNLength(row[1]);
|
|
185
|
+
if (result.scale[1] == 0) {
|
|
186
|
+
return NO;
|
|
187
|
+
}
|
|
188
|
+
ANNCombine(row[1], row[1], 1 / result.scale[1], 0);
|
|
189
|
+
result.skew[0] /= result.scale[1];
|
|
190
|
+
result.skew[1] = ANNDot(row[0], row[2]);
|
|
191
|
+
ANNCombine(row[2], row[0], 1, -result.skew[1]);
|
|
192
|
+
result.skew[2] = ANNDot(row[1], row[2]);
|
|
193
|
+
ANNCombine(row[2], row[1], 1, -result.skew[2]);
|
|
194
|
+
result.scale[2] = ANNLength(row[2]);
|
|
195
|
+
if (result.scale[2] == 0) {
|
|
196
|
+
return NO;
|
|
197
|
+
}
|
|
198
|
+
ANNCombine(row[2], row[2], 1 / result.scale[2], 0);
|
|
199
|
+
result.skew[1] /= result.scale[2];
|
|
200
|
+
result.skew[2] /= result.scale[2];
|
|
201
|
+
// The rows are orthonormal now; a mirrored system turns into a rotation with every scale negated. A flat transform
|
|
202
|
+
// (nothing out of the screen's plane) negates only x and keeps its rotation in the plane, as Android's views hold a
|
|
203
|
+
// mirror: it then flips across the element's own axis on the way, on both platforms, rather than through 3D.
|
|
204
|
+
const double cross[3] = {row[1][1] * row[2][2] - row[1][2] * row[2][1],
|
|
205
|
+
row[1][2] * row[2][0] - row[1][0] * row[2][2],
|
|
206
|
+
row[1][0] * row[2][1] - row[1][1] * row[2][0]};
|
|
207
|
+
if (ANNDot(row[0], cross) < 0) {
|
|
208
|
+
const double flatness = std::fabs(m[0][2]) + std::fabs(m[1][2]) + std::fabs(m[2][0]) +
|
|
209
|
+
std::fabs(m[2][1]) + std::fabs(m[0][3]) + std::fabs(m[1][3]) +
|
|
210
|
+
std::fabs(m[2][3]);
|
|
211
|
+
const int negated = flatness < 1e-9 ? 1 : 3;
|
|
212
|
+
for (int i = 0; i < negated; i++) {
|
|
213
|
+
result.scale[i] = -result.scale[i];
|
|
214
|
+
ANNCombine(row[i], row[i], -1, 0);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// The quaternion by Shepperd's method, from the largest of its four terms: the signs of the others follow from
|
|
218
|
+
// the off-diagonal terms, also for half a turn (w = 0), where the comparisons of the pseudo-code lose them. `r` is
|
|
219
|
+
// the rotation for column vectors, the transpose of the rows.
|
|
220
|
+
auto r = [&row](int i, int j) {
|
|
221
|
+
return row[j][i];
|
|
222
|
+
};
|
|
223
|
+
double *q = result.quaternion;
|
|
224
|
+
double trace = r(0, 0) + r(1, 1) + r(2, 2);
|
|
225
|
+
if (trace > 0) {
|
|
226
|
+
double s = 2 * std::sqrt(trace + 1);
|
|
227
|
+
q[3] = s / 4;
|
|
228
|
+
q[0] = (r(2, 1) - r(1, 2)) / s;
|
|
229
|
+
q[1] = (r(0, 2) - r(2, 0)) / s;
|
|
230
|
+
q[2] = (r(1, 0) - r(0, 1)) / s;
|
|
231
|
+
} else if (r(0, 0) > r(1, 1) && r(0, 0) > r(2, 2)) {
|
|
232
|
+
double s = 2 * std::sqrt(1 + r(0, 0) - r(1, 1) - r(2, 2));
|
|
233
|
+
q[3] = (r(2, 1) - r(1, 2)) / s;
|
|
234
|
+
q[0] = s / 4;
|
|
235
|
+
q[1] = (r(0, 1) + r(1, 0)) / s;
|
|
236
|
+
q[2] = (r(0, 2) + r(2, 0)) / s;
|
|
237
|
+
} else if (r(1, 1) > r(2, 2)) {
|
|
238
|
+
double s = 2 * std::sqrt(1 + r(1, 1) - r(0, 0) - r(2, 2));
|
|
239
|
+
q[3] = (r(0, 2) - r(2, 0)) / s;
|
|
240
|
+
q[0] = (r(0, 1) + r(1, 0)) / s;
|
|
241
|
+
q[1] = s / 4;
|
|
242
|
+
q[2] = (r(1, 2) + r(2, 1)) / s;
|
|
243
|
+
} else {
|
|
244
|
+
double s = 2 * std::sqrt(1 + r(2, 2) - r(0, 0) - r(1, 1));
|
|
245
|
+
q[3] = (r(1, 0) - r(0, 1)) / s;
|
|
246
|
+
q[0] = (r(0, 2) + r(2, 0)) / s;
|
|
247
|
+
q[1] = (r(1, 2) + r(2, 1)) / s;
|
|
248
|
+
q[2] = s / 4;
|
|
249
|
+
}
|
|
250
|
+
*parts = result;
|
|
251
|
+
return YES;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// The rotation of a unit quaternion as a matrix of row vectors: the transpose of the decomposition's pseudo-code,
|
|
255
|
+
// whose matrix is written for column vectors there.
|
|
256
|
+
static void ANNRotationMatrixOf(const double q[4], ANNMatrix m) {
|
|
257
|
+
double x = q[0], y = q[1], z = q[2], w = q[3];
|
|
258
|
+
ANNMatrixIdentity(m);
|
|
259
|
+
m[0][0] = 1 - 2 * (y * y + z * z);
|
|
260
|
+
m[0][1] = 2 * (x * y + z * w);
|
|
261
|
+
m[0][2] = 2 * (x * z - y * w);
|
|
262
|
+
m[1][0] = 2 * (x * y - z * w);
|
|
263
|
+
m[1][1] = 1 - 2 * (x * x + z * z);
|
|
264
|
+
m[1][2] = 2 * (y * z + x * w);
|
|
265
|
+
m[2][0] = 2 * (x * z + y * w);
|
|
266
|
+
m[2][1] = 2 * (y * z - x * w);
|
|
267
|
+
m[2][2] = 1 - 2 * (x * x + y * y);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The shears as the decomposition took them off the rows: row 1 gains xy of row 0, row 2 xz of row 0 and yz of row 1.
|
|
271
|
+
static void ANNSkewMatrixOf(const double skew[3], ANNMatrix m) {
|
|
272
|
+
ANNMatrixIdentity(m);
|
|
273
|
+
m[1][0] = skew[0];
|
|
274
|
+
m[2][0] = skew[1];
|
|
275
|
+
m[2][1] = skew[2];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
CATransform3D ANNTransformOfParts(ANNTransformParts parts) {
|
|
279
|
+
// Built from the last step back: perspective, translation, rotation, skew, then the scale goes first.
|
|
280
|
+
ANNMatrix m;
|
|
281
|
+
ANNMatrixIdentity(m);
|
|
282
|
+
for (int i = 0; i < 4; i++) {
|
|
283
|
+
m[i][3] = parts.perspective[i];
|
|
284
|
+
}
|
|
285
|
+
for (int i = 0; i < 4; i++) {
|
|
286
|
+
for (int j = 0; j < 3; j++) {
|
|
287
|
+
m[3][i] += parts.translate[j] * m[j][i];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
ANNMatrix rotation;
|
|
291
|
+
ANNRotationMatrixOf(parts.quaternion, rotation);
|
|
292
|
+
ANNMatrixMultiply(rotation, m, m);
|
|
293
|
+
ANNMatrix skew;
|
|
294
|
+
ANNSkewMatrixOf(parts.skew, skew);
|
|
295
|
+
ANNMatrixMultiply(skew, m, m);
|
|
296
|
+
for (int i = 0; i < 3; i++) {
|
|
297
|
+
for (int j = 0; j < 4; j++) {
|
|
298
|
+
m[i][j] *= parts.scale[i];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return ANNTransformOfMatrix(m);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static double ANNLerp(double from, double to, double f) {
|
|
305
|
+
return from + f * (to - from);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
ANNTransformParts ANNInterpolateTransformParts(ANNTransformParts from, ANNTransformParts to,
|
|
309
|
+
double f) {
|
|
310
|
+
if (f == 0) {
|
|
311
|
+
return from;
|
|
312
|
+
}
|
|
313
|
+
if (f == 1) {
|
|
314
|
+
return to;
|
|
315
|
+
}
|
|
316
|
+
ANNTransformParts parts;
|
|
317
|
+
for (int i = 0; i < 3; i++) {
|
|
318
|
+
parts.translate[i] = ANNLerp(from.translate[i], to.translate[i], f);
|
|
319
|
+
parts.scale[i] = ANNLerp(from.scale[i], to.scale[i], f);
|
|
320
|
+
parts.skew[i] = ANNLerp(from.skew[i], to.skew[i], f);
|
|
321
|
+
}
|
|
322
|
+
for (int i = 0; i < 4; i++) {
|
|
323
|
+
parts.perspective[i] = ANNLerp(from.perspective[i], to.perspective[i], f);
|
|
324
|
+
}
|
|
325
|
+
// The slerp, along the shorter arc: q and −q are the same rotation.
|
|
326
|
+
const double *a = from.quaternion;
|
|
327
|
+
double b[4] = {to.quaternion[0], to.quaternion[1], to.quaternion[2], to.quaternion[3]};
|
|
328
|
+
double product = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
|
329
|
+
if (product < 0) {
|
|
330
|
+
for (int i = 0; i < 4; i++) {
|
|
331
|
+
b[i] = -b[i];
|
|
332
|
+
}
|
|
333
|
+
product = -product;
|
|
334
|
+
}
|
|
335
|
+
product = std::min(product, 1.0);
|
|
336
|
+
if (product >= 1.0 - 1e-12) {
|
|
337
|
+
for (int i = 0; i < 4; i++) {
|
|
338
|
+
parts.quaternion[i] = ANNLerp(a[i], b[i], f);
|
|
339
|
+
}
|
|
340
|
+
return parts;
|
|
341
|
+
}
|
|
342
|
+
double theta = std::acos(product);
|
|
343
|
+
double sinTheta = std::sqrt(1 - product * product);
|
|
344
|
+
double wa = std::sin((1 - f) * theta) / sinTheta;
|
|
345
|
+
double wb = std::sin(f * theta) / sinTheta;
|
|
346
|
+
for (int i = 0; i < 4; i++) {
|
|
347
|
+
parts.quaternion[i] = a[i] * wa + b[i] * wb;
|
|
348
|
+
}
|
|
349
|
+
return parts;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
void ANNRotationDegreesOf(ANNTransformParts parts, double *x, double *y, double *z) {
|
|
353
|
+
ANNMatrix r;
|
|
354
|
+
ANNRotationMatrixOf(parts.quaternion, r);
|
|
355
|
+
// r = Rx(a)·Ry(b)·Rz(c) in row vectors: r[0][2] = −sin b, r[1][2] = sin a·cos b, r[2][2] = cos a·cos b,
|
|
356
|
+
// r[0][1] = cos b·sin c, r[0][0] = cos b·cos c.
|
|
357
|
+
double sinB = std::max(-1.0, std::min(1.0, -r[0][2]));
|
|
358
|
+
double b = std::asin(sinB);
|
|
359
|
+
double a;
|
|
360
|
+
double c;
|
|
361
|
+
if (std::fabs(std::cos(b)) > 1e-6) {
|
|
362
|
+
a = std::atan2(r[1][2], r[2][2]);
|
|
363
|
+
c = std::atan2(r[0][1], r[0][0]);
|
|
364
|
+
} else {
|
|
365
|
+
// Turned a quarter about y: x and z turn about one axis, all of it goes to x.
|
|
366
|
+
c = 0;
|
|
367
|
+
a = std::atan2(sinB * r[1][0], r[1][1]);
|
|
368
|
+
}
|
|
369
|
+
const double degrees = 180.0 / M_PI;
|
|
370
|
+
*x = a * degrees;
|
|
371
|
+
*y = b * degrees;
|
|
372
|
+
*z = c * degrees;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
CATransform3D ANNSkewTransformOf(ANNTransformParts parts) {
|
|
376
|
+
ANNMatrix m;
|
|
377
|
+
ANNSkewMatrixOf(parts.skew, m);
|
|
378
|
+
return ANNTransformOfMatrix(m);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#if DEBUG
|
|
382
|
+
|
|
383
|
+
static BOOL ANNSameTransform(CATransform3D a, CATransform3D b, double tolerance) {
|
|
384
|
+
ANNMatrix ma;
|
|
385
|
+
ANNMatrix mb;
|
|
386
|
+
ANNMatrixOf(a, ma);
|
|
387
|
+
ANNMatrixOf(b, mb);
|
|
388
|
+
for (int i = 0; i < 4; i++) {
|
|
389
|
+
for (int j = 0; j < 4; j++) {
|
|
390
|
+
if (!(std::fabs(ma[i][j] - mb[i][j]) <= tolerance)) {
|
|
391
|
+
return NO;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return YES;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
static void ANNExpectTransform(const char *what, CATransform3D value, CATransform3D expected) {
|
|
399
|
+
if (!ANNSameTransform(value, expected, 1e-6)) {
|
|
400
|
+
RCTLogWarn(@"[ApexNavigation] transform self-check: %s is off", what);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
static CATransform3D ANNRotationZ(double degrees) {
|
|
405
|
+
return CATransform3DMakeRotation(degrees * M_PI / 180.0, 0, 0, 1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The transform at f of the way from `from` to `to`, split and rebuilt.
|
|
409
|
+
static CATransform3D ANNTransformBetween(CATransform3D from, CATransform3D to, double f) {
|
|
410
|
+
ANNTransformParts a;
|
|
411
|
+
ANNTransformParts b;
|
|
412
|
+
if (!ANNTransformPartsOf(from, &a) || !ANNTransformPartsOf(to, &b)) {
|
|
413
|
+
return CATransform3DIdentity;
|
|
414
|
+
}
|
|
415
|
+
return ANNTransformOfParts(ANNInterpolateTransformParts(a, b, f));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
void ANNTransformPartsSelfCheck(void) {
|
|
419
|
+
static dispatch_once_t once;
|
|
420
|
+
dispatch_once(&once, ^{
|
|
421
|
+
CATransform3D tilt = CATransform3DMakeRotation(40 * M_PI / 180.0, 1, 0, 0);
|
|
422
|
+
tilt.m34 = -1.0 / 500;
|
|
423
|
+
CATransform3D shear = CATransform3DIdentity;
|
|
424
|
+
shear.m21 = 0.3;
|
|
425
|
+
const struct {
|
|
426
|
+
const char *name;
|
|
427
|
+
CATransform3D transform;
|
|
428
|
+
} splits[] = {
|
|
429
|
+
{"a rotation of 90", ANNRotationZ(90)},
|
|
430
|
+
{"a rotation of 180", ANNRotationZ(180)},
|
|
431
|
+
{"a scale, a rotation and a translation",
|
|
432
|
+
CATransform3DConcat(CATransform3DConcat(CATransform3DMakeScale(2, 0.5, 1),
|
|
433
|
+
ANNRotationZ(30)),
|
|
434
|
+
CATransform3DMakeTranslation(12, -7, 0))},
|
|
435
|
+
{"a skew", CATransform3DConcat(shear, ANNRotationZ(-20))},
|
|
436
|
+
{"a tilt with perspective", tilt},
|
|
437
|
+
{"a mirror", CATransform3DMakeScale(-1, 1, 1)},
|
|
438
|
+
{"a mirror and a turn of 30",
|
|
439
|
+
CATransform3DConcat(ANNRotationZ(30), CATransform3DMakeScale(-1, 1, 1))},
|
|
440
|
+
{"a turn of -90 and a mirror",
|
|
441
|
+
CATransform3DConcat(CATransform3DMakeScale(-1, 1, 1), ANNRotationZ(-90))},
|
|
442
|
+
{"a mirror upside down and a turn of -30",
|
|
443
|
+
CATransform3DConcat(ANNRotationZ(-30), CATransform3DMakeScale(1, -1, 1))},
|
|
444
|
+
{"half a turn and a tilt",
|
|
445
|
+
CATransform3DConcat(ANNRotationZ(180),
|
|
446
|
+
CATransform3DMakeRotation(20 * M_PI / 180.0, 1, 0, 0))},
|
|
447
|
+
};
|
|
448
|
+
for (const auto &split : splits) {
|
|
449
|
+
ANNTransformParts parts;
|
|
450
|
+
if (!ANNTransformPartsOf(split.transform, &parts)) {
|
|
451
|
+
RCTLogWarn(@"[ApexNavigation] transform self-check: %s can't be split", split.name);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
ANNExpectTransform(split.name, ANNTransformOfParts(parts), split.transform);
|
|
455
|
+
}
|
|
456
|
+
ANNExpectTransform("0 to 90 half way",
|
|
457
|
+
ANNTransformBetween(ANNRotationZ(0), ANNRotationZ(90), 0.5),
|
|
458
|
+
ANNRotationZ(45));
|
|
459
|
+
ANNExpectTransform("45 to -45 half way",
|
|
460
|
+
ANNTransformBetween(ANNRotationZ(45), ANNRotationZ(-45), 0.5),
|
|
461
|
+
CATransform3DIdentity);
|
|
462
|
+
ANNExpectTransform("0 to 270 half way, the shorter arc",
|
|
463
|
+
ANNTransformBetween(ANNRotationZ(0), ANNRotationZ(270), 0.5),
|
|
464
|
+
ANNRotationZ(-45));
|
|
465
|
+
ANNExpectTransform("170 to -170 half way, the shorter arc",
|
|
466
|
+
ANNTransformBetween(ANNRotationZ(170), ANNRotationZ(-170), 0.5),
|
|
467
|
+
ANNRotationZ(180));
|
|
468
|
+
ANNExpectTransform("a scale half way",
|
|
469
|
+
ANNTransformBetween(CATransform3DIdentity,
|
|
470
|
+
CATransform3DMakeScale(3, 2, 1), 0.5),
|
|
471
|
+
CATransform3DMakeScale(2, 1.5, 1));
|
|
472
|
+
// Half a turn keeps its size on the way: a quarter turn, either way, half way.
|
|
473
|
+
CATransform3D half = ANNTransformBetween(ANNRotationZ(0), ANNRotationZ(180), 0.5);
|
|
474
|
+
if (!ANNSameTransform(half, ANNRotationZ(90), 1e-6) &&
|
|
475
|
+
!ANNSameTransform(half, ANNRotationZ(-90), 1e-6)) {
|
|
476
|
+
RCTLogWarn(@"[ApexNavigation] transform self-check: 0 to 180 half way is off");
|
|
477
|
+
}
|
|
478
|
+
// A flat mirror turned keeps its rotation in the plane, the mirror in x, as Android's views hold it.
|
|
479
|
+
ANNTransformParts mirrored;
|
|
480
|
+
CATransform3D mirror =
|
|
481
|
+
CATransform3DConcat(ANNRotationZ(30), CATransform3DMakeScale(-1, 1, 1));
|
|
482
|
+
if (ANNTransformPartsOf(mirror, &mirrored)) {
|
|
483
|
+
BOOL flat = std::fabs(mirrored.quaternion[0]) < 1e-9 &&
|
|
484
|
+
std::fabs(mirrored.quaternion[1]) < 1e-9;
|
|
485
|
+
if (!(mirrored.scale[0] < 0 && mirrored.scale[1] > 0 && flat)) {
|
|
486
|
+
RCTLogWarn(@"[ApexNavigation] transform self-check: a flat mirror leaves the plane");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// The turns about x, y and z rebuild the rotation in the order of a view's tracks.
|
|
490
|
+
CATransform3D turned = CATransform3DConcat(
|
|
491
|
+
CATransform3DConcat(CATransform3DMakeRotation(25 * M_PI / 180.0, 1, 0, 0),
|
|
492
|
+
CATransform3DMakeRotation(-35 * M_PI / 180.0, 0, 1, 0)),
|
|
493
|
+
ANNRotationZ(120));
|
|
494
|
+
ANNTransformParts turnedParts;
|
|
495
|
+
if (ANNTransformPartsOf(turned, &turnedParts)) {
|
|
496
|
+
double x = 0, y = 0, z = 0;
|
|
497
|
+
ANNRotationDegreesOf(turnedParts, &x, &y, &z);
|
|
498
|
+
CATransform3D rebuilt = CATransform3DConcat(
|
|
499
|
+
CATransform3DConcat(CATransform3DMakeRotation(x * M_PI / 180.0, 1, 0, 0),
|
|
500
|
+
CATransform3DMakeRotation(y * M_PI / 180.0, 0, 1, 0)),
|
|
501
|
+
ANNRotationZ(z));
|
|
502
|
+
ANNExpectTransform("the turns about x, y and z", rebuilt, turned);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
#endif
|
|
@@ -17,10 +17,12 @@ FOUNDATION_EXPORT CGFloat ANNCameraDistance(void);
|
|
|
17
17
|
/// `rotationY`, `rotation` (clockwise, degrees), the perspective, the translation.
|
|
18
18
|
///
|
|
19
19
|
/// A missing end is the view's value when the tracks are made: `x`, `y` its place in its parent (from its layer's
|
|
20
|
-
/// position and bounds, never its frame)
|
|
21
|
-
///
|
|
22
|
-
///
|
|
23
|
-
///
|
|
20
|
+
/// position and bounds, never its frame) moved by its own translation, `alpha` its alpha before any hide, the
|
|
21
|
+
/// translations, scales and rotations those of its own transform (React Native's `transform`, split into its parts as
|
|
22
|
+
/// Android's views hold it: the shorter angle, −180…180). A property that doesn't play keeps the view's own value, its
|
|
23
|
+
/// skew and perspective stay. Each frame writes only what plays: `alpha` only with an `alpha` track, `layer.transform`
|
|
24
|
+
/// only when a property of the transform plays; with `translationX` and `x` on one side the translation wins. The frame
|
|
25
|
+
/// is never changed (React keeps the layout). `restore` puts back the alpha and the transform as they were read.
|
|
24
26
|
@interface ANNViewTracks : NSObject <ANNFrameClient>
|
|
25
27
|
|
|
26
28
|
/// The playing properties of `side` on `view`. `alphaBefore`: the view's alpha before the core hid it for the
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "ANNAnimationPropertyOptions.h"
|
|
3
3
|
#import "ANNElementTransitionOptions.h"
|
|
4
4
|
#import "ANNTrack.h"
|
|
5
|
+
#import "ANNTransformParts.h"
|
|
5
6
|
#import "ANNUtils.h"
|
|
6
7
|
#import "ANNViewAnimationOptions.h"
|
|
7
8
|
#import <QuartzCore/QuartzCore.h>
|
|
@@ -81,6 +82,15 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
81
82
|
CGPoint _origin;
|
|
82
83
|
CGFloat _alphaBefore;
|
|
83
84
|
CATransform3D _transformBefore;
|
|
85
|
+
// The parts of the view's own transform (its React Native `transform`): what a property that doesn't play keeps,
|
|
86
|
+
// and the missing end of one that does, as Android keeps a view's own properties.
|
|
87
|
+
CGPoint _ownTranslation;
|
|
88
|
+
CGPoint _ownScale;
|
|
89
|
+
double _ownRotationX;
|
|
90
|
+
double _ownRotationY;
|
|
91
|
+
double _ownRotation;
|
|
92
|
+
CATransform3D _ownSkew;
|
|
93
|
+
CGFloat _ownPerspective;
|
|
84
94
|
BOOL _animatesTransform;
|
|
85
95
|
BOOL _tilts;
|
|
86
96
|
BOOL _wroteAlpha;
|
|
@@ -146,21 +156,48 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
146
156
|
layer.position.y - bounds.size.height * layer.anchorPoint.y);
|
|
147
157
|
_alphaBefore = alphaBefore;
|
|
148
158
|
_transformBefore = layer.transform;
|
|
159
|
+
[self readOwnTransform];
|
|
149
160
|
}
|
|
150
161
|
return self;
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
// A transform that can't be split (a scale of 0) counts as none, as before the view's own transform was kept.
|
|
165
|
+
- (void)readOwnTransform {
|
|
166
|
+
_ownScale = CGPointMake(1, 1);
|
|
167
|
+
_ownSkew = CATransform3DIdentity;
|
|
168
|
+
ANNTransformParts parts;
|
|
169
|
+
if (!ANNTransformPartsOf(_transformBefore, &parts)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
_ownTranslation = CGPointMake((CGFloat)parts.translate[0], (CGFloat)parts.translate[1]);
|
|
173
|
+
_ownScale = CGPointMake((CGFloat)parts.scale[0], (CGFloat)parts.scale[1]);
|
|
174
|
+
ANNRotationDegreesOf(parts, &_ownRotationX, &_ownRotationY, &_ownRotation);
|
|
175
|
+
_ownSkew = ANNSkewTransformOf(parts);
|
|
176
|
+
_ownPerspective = (CGFloat)parts.perspective[2];
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
- (double)viewValueOf:(ANNViewProperty)property {
|
|
154
180
|
switch (property) {
|
|
155
181
|
case ANNViewPropertyX:
|
|
156
|
-
return _origin.x;
|
|
182
|
+
return _origin.x + _ownTranslation.x;
|
|
157
183
|
case ANNViewPropertyY:
|
|
158
|
-
return _origin.y;
|
|
184
|
+
return _origin.y + _ownTranslation.y;
|
|
185
|
+
case ANNViewPropertyTranslationX:
|
|
186
|
+
return _ownTranslation.x;
|
|
187
|
+
case ANNViewPropertyTranslationY:
|
|
188
|
+
return _ownTranslation.y;
|
|
159
189
|
case ANNViewPropertyAlpha:
|
|
160
190
|
return _alphaBefore;
|
|
161
191
|
case ANNViewPropertyScaleX:
|
|
192
|
+
return _ownScale.x;
|
|
162
193
|
case ANNViewPropertyScaleY:
|
|
163
|
-
return
|
|
194
|
+
return _ownScale.y;
|
|
195
|
+
case ANNViewPropertyRotationX:
|
|
196
|
+
return _ownRotationX;
|
|
197
|
+
case ANNViewPropertyRotationY:
|
|
198
|
+
return _ownRotationY;
|
|
199
|
+
case ANNViewPropertyRotation:
|
|
200
|
+
return _ownRotation;
|
|
164
201
|
default:
|
|
165
202
|
return 0;
|
|
166
203
|
}
|
|
@@ -221,22 +258,25 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
221
258
|
? (CGFloat)[self valueOf:ANNViewPropertyTranslationY at:time]
|
|
222
259
|
: (CGFloat)([self valueOf:ANNViewPropertyY at:time] - _origin.y);
|
|
223
260
|
// `CATransform3D` multiplies row vectors: each step below comes after the ones before it.
|
|
224
|
-
CATransform3D transform =
|
|
225
|
-
|
|
261
|
+
CATransform3D transform =
|
|
262
|
+
CATransform3DConcat(CATransform3DMakeScale(scaleX, scaleY, 1), _ownSkew);
|
|
263
|
+
if (_tracks[ANNViewPropertyRotationX] || _ownRotationX != 0) {
|
|
226
264
|
CGFloat radians = ANNRadians([self valueOf:ANNViewPropertyRotationX at:time]);
|
|
227
265
|
transform = CATransform3DConcat(transform, ANNRotationX(radians));
|
|
228
266
|
}
|
|
229
|
-
if (_tracks[ANNViewPropertyRotationY]) {
|
|
267
|
+
if (_tracks[ANNViewPropertyRotationY] || _ownRotationY != 0) {
|
|
230
268
|
CGFloat radians = ANNRadians([self valueOf:ANNViewPropertyRotationY at:time]);
|
|
231
269
|
transform = CATransform3DConcat(transform, ANNRotationY(radians));
|
|
232
270
|
}
|
|
233
|
-
if (_tracks[ANNViewPropertyRotation]) {
|
|
271
|
+
if (_tracks[ANNViewPropertyRotation] || _ownRotation != 0) {
|
|
234
272
|
CGFloat radians = ANNRadians([self valueOf:ANNViewPropertyRotation at:time]);
|
|
235
273
|
transform = CATransform3DConcat(transform, ANNRotationZ(radians));
|
|
236
274
|
}
|
|
237
|
-
|
|
275
|
+
// A tilt that plays gets the camera's perspective unless the view has its own; a view's own tilt keeps the
|
|
276
|
+
// perspective it had, none without one, as React Native draws it.
|
|
277
|
+
if (_tilts || _ownPerspective != 0) {
|
|
238
278
|
CATransform3D perspective = CATransform3DIdentity;
|
|
239
|
-
perspective.m34 = -1.0 / ANNCameraDistance();
|
|
279
|
+
perspective.m34 = _ownPerspective != 0 ? _ownPerspective : -1.0 / ANNCameraDistance();
|
|
240
280
|
transform = CATransform3DConcat(transform, perspective);
|
|
241
281
|
}
|
|
242
282
|
transform = CATransform3DConcat(transform,
|