Its simple, I need to make a label in my project that says the current g-force from the accelerometer. I have found some code but I don't know where to put it in my code. I am guessing that I need to put it into a void statement but I am not sure. Please point me in the right direction.
sx = acceleration.x * kFilteringFactor + sx * (1.0 - kFilteringFactor);
sy = acceleration.y * kFilteringFactor + sy * (1.0 - kFilteringFactor);
sz = acceleration.z * kFilteringFactor + sz * (1.0 - kFilteringFactor);
float aValue = sqrt(sx*sx+sy*sy+sz*sz);
[gforcelabel setText:[NSString stringWithFormat:#"%.2f",aValue]];
I wouldn't bother with UIAccelerometerDelegate anymore considering it's unlikely at this point you'll need to maintain compatibility for anything below iOS 5. As linked in the other answer, you'll want to use Core Motion instead, specifically, CMMotionManager, which is available in iOS 5 and up. It's easy enough to use too, all you have to do is create an instance of the motion manager (you can only have one), set it's update interval (optional), and start monitoring the specified motion events.
if (!self.manager) {
self.manager = [CMMotionManager new];
}
if (self.manager.isAccelerometerActive) {
[self.manager stopAccelerometerUpdates];
}
NSOperationQueue *queue = [NSOperationQueue new];
[self.manager setAccelerometerUpdateInterval:1.0 / 30.0]; // 30 Updates Per Second
[self.manager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
NSLog(#"X: %f Y: %f Z: %f",accelerometerData.acceleration.x,accelerometerData.acceleration.y,accelerometerData.acceleration.z);
}];
The code I've provided creates a new operation queue on which to process the accelerometer updates, and if you want to update the UI from within the block, you'll have to move back to the main thread first. If you aren't doing any heavy processing within the block, and just need to update the UI (as it looks like you do), you can simply pass [NSOperationQueue mainQueue], instead of creating a new queue.
It's also worth noting that the motion manager can do more than just monitor acceleration data, it also has update blocks for the magnetometer, gyroscope, and device motion. Info can be found in the documentation
UIAccelerometer was deprecated in iOS 5.0. You should use the Core Motion Library instead.
If we suppose that you are targeting iOS 5.0, the code you've listed above would live in a view controller that implements the UIAccelerometerDelegate Protocol. To be specific, you would define the following protocol method and move your code inside the body of this method:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
I figured out exactly how to do it...
Viewcontroller.h
IBOutlet UILabel *currentg;
IBOutlet UILabel *avgAccelLabel;
float avgAccel;
float currentAccel;
int accelCount;
float accelX;
float accelY;
float accelZ;
UIAccelerometer *accelerometer;
Viewcontroller.m
//Use a basic low-pass filter to only keep the gravity in the accelerometer values
accelX = acceleration.x; //* kFilteringFactor + accelX * (1.0 - kFilteringFactor);
accelY = acceleration.y; //* kFilteringFactor + accelY * (1.0 - kFilteringFactor);
accelZ = acceleration.z; //* kFilteringFactor + accelZ * (1.0 - kFilteringFactor);
//calculate acceleration resultant
float accel = sqrt((ABS(accelX)*ABS(accelX))+(ABS(accelY)*ABS(accelY))+ (ABS(accelZ)*ABS(accelZ)));
//keep track of average accel
avgAccel = ((avgAccel * accelCount) + accel)/(accelCount + 1);
currentAccel = (accel);
accelCount++;
//display avg accel
NSString *avgAccelString = [NSString stringWithFormat:#"%.2f%#", avgAccel, #""];
avgAccelLabel.text = avgAccelString;
NSString *currentAccellString = [NSString stringWithFormat:#"%.2f%#",currentAccel, #"" ];
currentg.text = currentAccellString;
viewdidload
self.accelerometer = [UIAccelerometer sharedAccelerometer];
self.accelerometer.updateInterval = 0.4;
self.accelerometer.delegate = self;
Related
I am trying to pan and seek forwards and backwards in my AVPlayer. It is kind of working but the basic math of determining where the pan is translated to the length of the asset is wrong. Can any one offer assistance?
- (void) handlePanGesture:(UIPanGestureRecognizer*)pan{
CGPoint translate = [pan translationInView:self.view];
CGFloat xCoord = translate.x;
double diff = (xCoord);
//NSLog(#"%F",diff);
CMTime duration = self.avPlayer.currentItem.asset.duration;
float seconds = CMTimeGetSeconds(duration);
NSLog(#"duration: %.2f", seconds);
CGFloat gh = 0;
if (diff>=0) {
//If the difference is positive
NSLog(#"%f",diff);
gh = diff;
} else {
//If the difference is negative
NSLog(#"%f",diff*-1);
gh = diff*-1;
}
float minValue = 0;
float maxValue = 1024;
float value = gh;
double time = seconds * (value - minValue) / (maxValue - minValue);
[_avPlayer seekToTime:CMTimeMakeWithSeconds(time, 10) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
//[_avPlayer seekToTime:CMTimeMakeWithSeconds(seconds*(Float64)diff , 1024) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}
You are not normalizing the touch location and the corresponding time values. Is there a 1:1 relationship between the two? That's not possible.
Take the minimum and maximum touch location values of the pan gesture and the minimum and maximum values of the asset's duration (obviously, from zero to the length of the video), and then apply the following formula to translate the touch location to the seek time:
// Map
#define map(x, in_min, in_max, out_min, out_max) ((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
Here's the code I wrote that uses that formula:
- (IBAction)handlePanGesture:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateChanged){
CGPoint location = [sender locationInView:self];
float nlx = ((location.x / ((CGRectGetMidX(self.frame) / (self.frame.size.width / 2.0)))) / (self.frame.size.width / 2.0)) - 1.0;
//float nly = ((location.y / ((CGRectGetMidY(self.view.frame) / (self.view.frame.size.width / 2.0)))) / (self.view.frame.size.width / 2.0)) - 1.0;
nlx = nlx * 2.0;
[self.delegate setRate:nlx];
}
}
I culled the label that displays the rate, and the Play icon that appears while you're scrubbing, and which changes sizes depending on how fast or slow you are panning the video. Although you didn't ask for that, if you want it, just ask.
Oh, the "times-two" factor is intended to add an acceleration curve to the pan gesture value sent to the delegate's setRate method. You can use any formula for that, even an actual curve, like pow(nlx, 2.0) or whatever...
If you want to make it more precise and useful you should implement different "levels of sensitivity".
Apple does that with their slider: if you drag away from the slider and then to the sides, that pace at which the video moves changes. The farther your are from the slider the more precise it gets/the less you can reach.
I've been trying to get my MKMapView to detect whether or not a tap was on a tile with alpha > 0. I'm quite new at ObjC and Xcode as well so this functionality is a bit over my head. All help will me greatly appreciated!
So far I've tried many different strategies but always come up short. We have custom classes to replace MKOverlay and MKOverlayView that implement each respectively so I've been trying to grab the tiles when they're created and save them to an array to later reference in the MKMapViewController when the map is touched.
- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale
{
NSInteger z = zoomScaleToZoomLevel(scale);
// Number of tiles wide or high (but not wide * high)
NSInteger tilesAtZ = pow(2, z);
NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
NSMutableArray *tiles = nil;
for (NSInteger x = minX; x <= maxX; x++) {
for (NSInteger y = minY; y <= maxY; y++) {
// As in initWithTilePath, need to flip y index to match the gdal2tiles.py convention.
NSInteger flippedY = abs(y + 1 - tilesAtZ);
NSString *tileKey = [[NSString alloc] initWithFormat:#"%d/%d/%d", z, x, flippedY];
if ([tilePaths containsObject:tileKey]) {
if (!tiles) {
tiles = [NSMutableArray array];
}
MKMapRect frame = MKMapRectMake((double)(x * TILE_SIZE) / scale,
(double)(y * TILE_SIZE) / scale,
TILE_SIZE / scale,
TILE_SIZE / scale);
NSString *path = [[NSString alloc] initWithFormat:#"%#/%#.png", tileBase, tileKey];
ImageTile *tile = [[ImageTile alloc] initWithFrame:frame path:path];
[tiles addObject:tile];
[myTiles addObject:tile];
[path release];
[tile release];
}
[tileKey release];
}
}
return tiles;
}
That's where I populate the array which is a "class variable". If I comment out the [tiles addObject:tile]; I get the background of the map drawn but no buildings so I think adding specifically those tiles is correct.
Then in the mapviewController gesture handler function I check if the touch is in the tile.frame which is is for 8 out of 32 (it can be 0 if you click far from the buildings and the total changes when you zoom around, but always gets bigger)which seems like an odd number. But pretending that that works correctly I check the alpha at that point using a modified version of this answerer's function: how to get the RGBA value of UIImage in the specific clicked point
but I don't know if that works for mapView's like it would for imageViews. I think I might need to translate the context but I've never worked with contexts before...
Sorry for so much text! Maybe this isn't even possible? I'll add more code if clarification is needed. Any input would help!
In Android, the API provides the field of view angle:
Camera.Parameters.getHorizontalViewAngle()
Camera.Parameters.getVerticalViewAngle()
What's the equivalent in iOS?
I don't want to pre-write those values because it's not flexible.
I'm not entirely sure what "horizontal" and "vertical" mean in this context, but I think of two calculations, the rotation about the "z" axis (i.e. how level we are with the horizon in the photo), and how much it's tilted forward and backward (i.e. the rotation about the "x" axis, namely is it pointing up or down). You can do this using Core Motion. Just add it to your project and then you can do something like:
Make sure to import CoreMotion header:
#import <CoreMotion/CoreMotion.h>
Define a few class properties:
#property (nonatomic, strong) CMMotionManager *motionManager;
#property (nonatomic, strong) NSOperationQueue *deviceQueue;
Start the motion manager:
- (void)startMotionManager
{
self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
toQueue:self.deviceQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CGFloat x = motion.gravity.x;
CGFloat y = motion.gravity.y;
CGFloat z = motion.gravity.z;
// how much is it rotated around the z axis
CGFloat rotationAngle = atan2(y, x) + M_PI_2; // in radians
CGFloat rotationAngleDegrees = rotationAngle * 180.0f / M_PI; // in degrees
// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltAngle = (r == 0.0 ? 0.0 : acosf(z/r); // in radians
CGFloat tiltAngleDegrees = tiltAngle * 180.0f / M_PI - 90.0f); // in degrees
}];
}];
}
When done, stop the motion manager:
- (void)stopMotionManager
{
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
self.deviceQueue = nil;
}
I'm not doing anything with the values here, but you can save them in class properties which you can then access elsewhere in your app. Or you could dispatch UI updates back to the main queue right from here. A bunch of options.
Since this is iOS 5 and higher, if the app is supporting earlier versions you might also want to weakly link Core Motion then then check to see everything is ok, and if not, just realize that you're not going to be capturing the orientation of the device:
if ([CMMotionManager class])
{
// ok, core motion exists
}
And, in case you're wondering about my fairly arbitrary choice of twelve times per second, in the Event Handling Guide for iOS, they suggest 10-20/second if just checking the orientation of the device.
In iOS 7.0+, you can obtain FOV angle of a camera by reading this property.
https://developer.apple.com/documentation/avfoundation/avcapturedeviceformat/1624569-videofieldofview?language=objc
AVCaptureDevice *camera;
camera = ...
float fov = [[camera activeFormat] videoFieldOfView];
NSLog("FOV=%f(deg)", fov);
Suppose you are holding an iphone/ipad vertically in front of you with the screen facing you, in portrait orientation. You tilt the device to one side, keeping the screen facing you. How do you measure that static tilt angle using CMMotionManager? It seems a simple question which should have a simple answer, yet I cannot find any method that does not disappear into quaternions and rotation matrices.
Can anyone point me to a worked example?
Look at gravity:
self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
// UIDevice *device = [UIDevice currentDevice];
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
toQueue:self.deviceQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CGFloat x = motion.gravity.x;
CGFloat y = motion.gravity.y;
CGFloat z = motion.gravity.z;
}];
}];
With this reference frame (CMAttitudeReferenceFrameXArbitraryZVertical), if z is near zero, you're holding it on a plane perpendicular with the ground (e.g. as if you were holding it against a wall) and as you rotate it on that plane, x and y values change. Vertical is where x is near zero and y is near -1.
Looking at this post, I notice that if you want to convert this vector into angles, you can use the following algorithms.
If you want to calculate how many degrees from vertical the device is rotated (where positive is clockwise, negative is counter-clockwise), you can calculate this as:
// how much is it rotated around the z axis
CGFloat angle = atan2(y, x) + M_PI_2; // in radians
CGFloat angleDegrees = angle * 180.0f / M_PI; // in degrees
You can use this to figure out how much to rotate the view via the Quartz 2D transform property:
self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);
(Personally, I update the rotation angle in the startDeviceMotionUpdates method, and update this transform in a CADisplayLink, which decouples the screen updates from the angle updates.)
You can see how far you've tilted it backward/forward via:
// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltForwardBackward = acosf(z/r) * 180.0f / M_PI - 90.0f;
It is kind of a late answer but you can found a working example on github and the blog article that comes with it.
To summarize the article mentioned above, you can use quaternions to avoid the gimbal lock problem that you are probably facing when holding the iPhone vertically.
Here is the coding part that compute the tilt (or yaw) :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
You can even add a simple Kalman filter to ease the yaw :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...
Here is an example that rotates a UIView self.horizon to keep it level with the horizon as you tilt the device.
- (void)startDeviceMotionUpdates
{
CMMotionManager* coreMotionManager = [[CMMotionManager alloc] init];
NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init]
CGFloat updateInterval = 1/60.0;
CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXArbitraryCorrectedZVertical;
[coreMotionManager setDeviceMotionUpdateInterval:updateInterval];
[coreMotionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
toQueue:motionQueue
withHandler:
^(CMDeviceMotion* motion, NSError* error){
CGFloat angle = atan2( motion.gravity.x, motion.gravity.y );
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
self.horizon.transform = transform;
}];
}
This is a little oversimplified - you should be sure to have only one instance of CMMotionManager in your app so you want to pre-initialise this and access it via a property.
Since iOS8 CoreMotion also returns you a CMAttitude object, which contains pitch, roll and yaw properties, as well as the quaternion. Using this will mean you don't have to do the manual maths to convert acceleration to orientation.
Alright, here we go. I have a cocos2d app, and there are targets that move toward the player. When the player moves, I would like for them to slowly change their destination toward the player again, so they aren't just moving into empty space. Is it possible to change the destination of a sprite mid-runAction?
edit:
This is the code in - (void)changeTargetDest
- (void)changeTargetDest {
NSMutableArray* deleteArray = [[NSMutableArray alloc] init];
for(CCSprite* s in _targets) {
float offX = s.position.x - player.position.x;
float offY = s.position.y - player.position.y;
float adjustX;
float adjustY;
float offDistance = sqrt(powf(offX, 2.0f) + powf(offY, 2.0f));
if(offDistance < 15) {
[deleteArray addObject:s];
deaths++;
[deathLabel setString:[NSString stringWithFormat:#"Deaths: %ld", deaths]];
if(deaths == 0)
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %ld.00", score]];
else
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %.2f", ((float)score / (float)deaths)]];
}
else {
adjustX = offX * .99;
adjustY = offY * .99;
CGPoint point = CGPointMake(player.position.x + adjustX, player.position.y + adjustY);
[s setPosition:point];
}//else
}//for
for (CCSprite *target in deleteArray) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
}
This works well, except for one problem. Because the new position is calculated by just taking .99 of the previous offset, the closer the target gets to the player, the more slowly it moves. How can I make its speed constant?
You can stop the action and run a new action each few frames in a scheduled method.
but the better way is to compute the position of targets according to players position and use setPosition to manualy change their positions each frame in your update method.