-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathUnityView+iOS.mm
More file actions
243 lines (217 loc) · 10.4 KB
/
Copy pathUnityView+iOS.mm
File metadata and controls
243 lines (217 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*
* This is a copy of Unity's UnityView+iOS.mm that it puts in your built XCode project. It has been modified to send
* Apple Pencil touch events to iOSApplePencil.hpp, which buffers them and sends them to Unity. The only modifications
* are the touch event handlers at the bottom, and some extra include lines.
*/
#if PLATFORM_IOS
#import "UnityView.h"
#import "UnityAppController+Rendering.h"
#include "OrientationSupport.h"
#include <math.h>
#include "iOSApplePencil.hpp"
extern bool _unityAppReady;
@interface UnityView () <UIPencilInteractionDelegate>
@property (nonatomic, readwrite) ScreenOrientation contentOrientation;
@end
@implementation UnityView (iOS)
bool addedPencilInteraction;
- (void)pencilInteractionDidTap:(UIPencilInteraction*)interaction API_AVAILABLE(ios(12.1)) {
if (!interaction.enabled)
return;
AddApplePencilBarrelTapEvent();
}
- (void)willRotateToOrientation:(UIInterfaceOrientation)toOrientation fromOrientation:(UIInterfaceOrientation)fromOrientation;
{
// to support the case of interface and unity content orientation being different
// we will cheat a bit:
// we will calculate transform between interface orientations and apply it to unity view orientation
// you can still tweak unity view as you see fit in AppController, but this is what you want in 99% of cases
ScreenOrientation to = ConvertToUnityScreenOrientation(toOrientation);
ScreenOrientation from = ConvertToUnityScreenOrientation(fromOrientation);
if (fromOrientation == UIInterfaceOrientationUnknown)
_curOrientation = to;
else
_curOrientation = OrientationAfterTransform(_curOrientation, TransformBetweenOrientations(from, to));
_viewIsRotating = YES;
}
- (void)didRotate
{
if (_shouldRecreateView)
{
[self recreateRenderingSurface];
}
_viewIsRotating = NO;
}
// - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesBegin(touches, event); }
// - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesMoved(touches, event); }
// - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesEnded(touches, event); }
// - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { UnitySendTouchesCancelled(touches, event); }
- (void)addApplePencilEventForTouch:(UITouch *)touch isEstimationUpdate:(BOOL)isEstimationUpdate isPredicted:(BOOL)isPredicted {
// I think preciseLocationInView has sub-pixel accuracy, vs locationInView which gives you pixels, maybe?
CGPoint location = [touch preciseLocationInView:self];
// convert the location from points to pixels and invert y
location.x *= self.contentScaleFactor;
location.y = (self.bounds.size.height - location.y) * self.contentScaleFactor;
// the touch force is scaled so an average touch is 1, but we'll rescale so 1 is the max possible force,
// which is more in line with what InputSystem does for other devices.
CGFloat pressure = [touch force] / [touch maximumPossibleForce];
// we have azimuth and altitude (both in radians)
CGFloat azimuth = [touch azimuthAngleInView:self];
CGFloat altitude = [touch altitudeAngle];
// InputSystem's Pen wants tiltX and tiltY, which each go from -1 to 1 with 0 being perpendicular to the
// screen, so we need to calculate those. I believe Windows uses that style of coordinates natively.
// adapted from https://gist.github.com/k3a/2903719bb42b48c9198d20c2d6f73ac1
CGFloat tiltXrad = 0, tiltYrad = 0;
if (altitude == M_PI/2) {
// perpendicular to the pad
tiltXrad = 0;
tiltYrad = 0;
} else if (altitude == 0) {
// when pen is laying on the pad it is impossible to precisely encode but at least approximate for 4 cases
if (azimuth > 7 * M_PI/4 || azimuth <= M_PI/4) {
// for azimuth == 0, the pen is on the positive Y axis
tiltXrad = 0;
tiltYrad = M_PI/2;
} else if (azimuth > M_PI/4 && azimuth <= 3 * M_PI/4) {
// for azimuth == M_PI/2 the pen is on the positive X axis
tiltXrad = M_PI/2;
tiltYrad = 0;
} else if (azimuth > 3 * M_PI/4 && azimuth <= 5 * M_PI/4) {
// for azimuth == M_PI, the pen is on the negative Y axis
tiltXrad = 0;
tiltYrad = -M_PI/2;
} else if (azimuth > 5 * M_PI/4 && azimuth <= 7 * M_PI/4) {
// for azimuth == M_PI + M_PI/2 pen on negative X axis
tiltXrad = -M_PI/2;
tiltYrad = 0;
}
} else {
CGFloat tanAlt = tanf(altitude); // tan(x) = sin(x)/cos(x)
tiltXrad = atanf(sinf(azimuth) / tanAlt);
tiltYrad = atanf(cosf(azimuth) / tanAlt);
}
CGFloat tiltX = tiltXrad / (M_PI/2);
CGFloat tiltY = tiltYrad / (M_PI/2);
// 'tip' means the pen is touching the screen, so not ended (3) or cancelled (4). estimation updates can come in
// after the touch has ended which will make Unity think it's a new stroke, so we have a workaround for that on the
// Unity side.
bool tip = touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled;
AddApplePencilEvent(location.x, location.y, tip, pressure, tiltX, tiltY,
(unsigned int) touch.estimatedPropertiesExpectingUpdates,
touch.estimationUpdateIndex != nil ? touch.estimationUpdateIndex.unsignedIntegerValue : 0,
isEstimationUpdate, isPredicted);
}
- (void)sendPencilTouchesToUnity:(NSSet *)touches withEvent:(UIEvent *)event {
// fixme - is it possible to have multiple pencil touches in one event? if so, we're sending predicted touches
// for each one when we only need to do it for the last one
for (UITouch *touch in touches) {
// if it's not a pencil touch, ignore it
if ((int) touch.type != UITouchTypePencil)
continue;
// get the high-fidelity touches (these include the existing touch)
NSArray *coalescedTouches = [event coalescedTouchesForTouch:touch];
for (UITouch *ct in coalescedTouches)
[self addApplePencilEventForTouch:ct isEstimationUpdate:false isPredicted:false];
// get any predicted touches too. it's important that the predicted ones get sent after real ones because
// the frontend wants to easily be able to discard these after any new real touches.
for (UITouch *pt in [event predictedTouchesForTouch:touch])
[self addApplePencilEventForTouch:pt isEstimationUpdate:false isPredicted:true];
}
// dispatch on the Unity main thread...I think this is required?
// dispatch_async(dispatch_get_main_queue(), ^{
FlushApplePencilEvents();
// });
}
- (NSSet *)getPencilTouches:(NSSet *)touches {
NSMutableSet *filteredTouches = [NSMutableSet setWithCapacity:touches.count];
for (UITouch *touch in touches) {
if ((int) touch.type == UITouchTypePencil)
[filteredTouches addObject:touch];
}
return filteredTouches;
}
- (NSSet *)getFingerTouches:(NSSet *)touches {
NSMutableSet *filteredTouches = [NSMutableSet setWithCapacity:touches.count];
for (UITouch *touch in touches) {
if ((int) touch.type == UITouchTypeDirect)
[filteredTouches addObject:touch];
}
return filteredTouches;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
// add the pencil interaction if it's not already there. should really put this in some kind of init function.
if (!addedPencilInteraction) {
if (@available(iOS 12.1, *)) {
UIPencilInteraction* pencilInteraction = [[UIPencilInteraction alloc] init];
pencilInteraction.delegate = self;
[self addInteraction:pencilInteraction];
}
addedPencilInteraction = true;
}
// fixme - can we ever get pencil touches and finger touches in the same event?
NSSet *fingerTouches = [self getFingerTouches:touches];
NSSet *pencilTouches = [self getPencilTouches:touches];
if (fingerTouches.count > 0) {
UnitySendTouchesBegin(fingerTouches, event);
}
if (pencilTouches.count > 0) {
if (ApplePencilHandlerIsEnabled())
[self sendPencilTouchesToUnity:pencilTouches withEvent:event];
else
UnitySendTouchesBegin(pencilTouches, event);
}
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
// fixme - can we ever get pencil touches and finger touches in the same event?
NSSet *fingerTouches = [self getFingerTouches:touches];
NSSet *pencilTouches = [self getPencilTouches:touches];
if (fingerTouches.count > 0) {
UnitySendTouchesMoved(fingerTouches, event);
}
if (pencilTouches.count > 0) {
if (ApplePencilHandlerIsEnabled())
[self sendPencilTouchesToUnity:pencilTouches withEvent:event];
else
UnitySendTouchesMoved(pencilTouches, event);
}
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
// fixme - can we ever get pencil touches and finger touches in the same event?
NSSet *fingerTouches = [self getFingerTouches:touches];
NSSet *pencilTouches = [self getPencilTouches:touches];
if (fingerTouches.count > 0) {
UnitySendTouchesEnded(fingerTouches, event);
}
if (pencilTouches.count > 0) {
if (ApplePencilHandlerIsEnabled())
[self sendPencilTouchesToUnity:pencilTouches withEvent:event];
else
UnitySendTouchesEnded(pencilTouches, event);
}
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
// fixme - can we ever get pencil touches and finger touches in the same event?
NSSet *fingerTouches = [self getFingerTouches:touches];
NSSet *pencilTouches = [self getPencilTouches:touches];
if (fingerTouches.count > 0) {
UnitySendTouchesCancelled(fingerTouches, event);
}
if (pencilTouches.count > 0) {
if (ApplePencilHandlerIsEnabled())
[self sendPencilTouchesToUnity:pencilTouches withEvent:event];
else
UnitySendTouchesCancelled(pencilTouches, event);
}
}
- (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches {
if (!ApplePencilHandlerIsEnabled())
return;
for (UITouch *t in touches)
[self addApplePencilEventForTouch:t isEstimationUpdate:true isPredicted:false];
// dispatch on the Unity main thread...I think this is required?
// dispatch_async(dispatch_get_main_queue(), ^{
FlushApplePencilEvents();
// });
}
@end
#endif // PLATFORM_IOS