Please bear with my long question, I am trying to make it as clear as possible.
What i am trying to do is, get the attitude(roll pitch and yaw) when a picture is taken using camera and then save the attitude values to nsuserdefaults. After saving, the orientation is changed and then try to bring the phone to the same attitude the picture was taken by constantly comparing the attitude values(saved and current).
For the purpose of interface, the user interface has 3 dots (one for each attitude parameter)on screen which guide to the correct orientation the picture was taken. On reaching the correct attitude the match flag is shown on screen.
I have been finding the roll, pitch and yaw values as:
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
myRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
myPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));
myYaw = radiansToDegrees(2*(quat.x*quat.y + quat.w*quat.z));
When i noticed there is some disparity in the yaw values, i searched and could find from here:link, that
yaw, pitch and roll from a quaternion you will have the same
problem as if you were using just yaw, pitch and roll. You have to use
quaternions EVERYWHERE in your code and forget about yaw, pitch and
roll
So now i guess i have to code everything again... Please could you be so kind to point me to a sample code for using Quaternion for this purpose?
Here is the code i am working on:
In the ViewController.m, within the image:didFinishSavingWithError:
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double tempYaw = radiansToDegrees(asin(2*(quat.x*quat.y + quat.w*quat.z)));
double tempRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
double tempPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));
if (savingGyroOrientation == YES) {
NSLog(#"Roll = %f degrees",tempRoll);
NSLog(#"Pitch = %f degrees",tempPitch);
NSLog(#"Yaw = %f degrees",tempYaw);
[self.deviceStatus setDouble:tempRoll forKey:#"DeviceRoll"];
[self.deviceStatus setDouble:tempPitch forKey:#"DevicePitch"];
[self.deviceStatus setDouble:tempYaw forKey:#"DeviceYaw"];
[self.deviceStatus synchronize];
savingGyroOrientation = NO;
checkingGyroOrientation = YES;
self.savingLabel.hidden = YES;
self.startTimerButton.hidden = NO;
}
savingGyroOrientation = NO;
checkingGyroOrientation = YES;
self.savingLabel.hidden = YES;
self.startTimerButton.hidden = NO;
}
if (timerRunning == YES) {
if (checkingGyroOrientation == YES) {
self.takePicButton.hidden = YES;
int xRoll, yPitch, xYaw, yYaw;
// Roll Checking
if (tempRoll >= [self.deviceStatus doubleForKey:#"DeviceRoll"]-1 && tempRoll <= [self.deviceStatus doubleForKey:#"DeviceRoll"]+1 ) {
[self.rollDot setFrame:CGRectMake(150, 195, 20, 20)];
self.rollToR.hidden = YES;
self.rollToL.hidden = YES;
self.rollDot.hidden = NO;
rollOk = YES;
}else{
rollOk = NO;
self.rollDot.hidden = YES;
self.rollToR.hidden = NO;
self.rollToL.hidden = NO;
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] < 0) {
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]);
self.rollToR.hidden = YES;
if (xRoll <= 0) {
[self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>= 300){
[self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] > 0){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]);
self.rollToL.hidden = YES;
if (xRoll <= 0) {
[self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>=300){
[self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] > 180){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]-360);
self.rollToR.hidden = YES;
if (xRoll <= 0) {
[self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>=300){
[self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] < -180){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]+360);
self.rollToL.hidden = YES;
if (xRoll <= 0) {
[self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll >= 300){
[self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
}
//Pitch Checking
if (tempPitch >= [self.deviceStatus doubleForKey:#"DevicePitch"]-1 && tempPitch <= [self.deviceStatus doubleForKey:#"DevicePitch"]+1) {
[self.pitchDot setFrame:CGRectMake(150, 195, 20, 20)];
self.pitchDot.hidden = NO;
self.pitchToDown.hidden = YES;
self.pitchToUp.hidden = YES;
pitchOk = YES;
}else{
pitchOk = NO;
self.pitchDot.hidden = YES;
self.pitchToDown.hidden = NO;
self.pitchToUp.hidden = NO;
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] < 0) {
yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]);
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
self.pitchToDown.hidden = YES;
if (yPitch <= 0) {
[self.pitchToUp setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToUp setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToUp setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] > 0){
yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]);
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
self.pitchToUp.hidden = YES;
if (yPitch <= 0) {
[self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] < -180){
yPitch = 195+tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] + 360;
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
// NSLog(#"*yPitch is %d",yPitch);
self.pitchToUp.hidden = YES;
self.pitchToDown.hidden = NO;
if (yPitch <= 0 ) {
[self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
}
if (tempYaw >= [self.deviceStatus doubleForKey:#"DeviceYaw"]-2 && tempYaw <= [self.deviceStatus doubleForKey:#"DeviceYaw"]+2) {
[self.yawDot setFrame:CGRectMake(150, 195, 20, 20)];
self.yawDot.hidden = NO;
self.rotateRight.hidden = YES;
self.rotateLeft.hidden = YES;
yawOk = YES;
}else{
yawOk = NO;
self.yawDot.hidden = YES;
self.rotateRight.hidden = NO;
self.rotateLeft.hidden = NO;
if (tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"] < 0 ) {
xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"xYaw is %d, yYaw is %d",xYaw,yYaw);
self.rotateRight.hidden = YES;
if (xYaw <=0 && yYaw >=390) {
[self.rotateLeft setFrame:CGRectMake(0, 390, 20, 20)];
}else{
[self.rotateLeft setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
}
}if (tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"] > 0){
xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"*xYaw is %d, yYaw is %d",xYaw,yYaw);
self.rotateLeft.hidden = YES;
if (xYaw >=300 && yYaw <=0) {
[self.rotateRight setFrame:CGRectMake(300, 0, 20, 20)];
}else{
[self.rotateRight setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
}
}
}
if (rollOk == YES && pitchOk == YES && yawOk ==YES) {
self.orientationOkay.hidden = NO;
self.centerCircle.hidden = YES;
self.rollDot.hidden = YES;
self.pitchDot .hidden =YES;
self.yawDot.hidden = YES;
[self.clickTimer invalidate];
self.clickTimer = nil;
self.takePicButton.hidden = NO;
timerRunning = NO;
[self.motionManager stopDeviceMotionUpdates];
[self.deviceStatus removeObjectForKey:#"DeviceRoll"];
[self.deviceStatus removeObjectForKey:#"DevicePitch"];
[self.deviceStatus removeObjectForKey:#"DeviceYaw"];
[self.deviceStatus removeObjectForKey:#"DeviceAngle"];
}else{
self.orientationOkay.hidden = YES;
if (flagger == YES) {
self.centerCircle.hidden = NO;
}
else{
self.centerCircle.hidden = YES;
}
}
}
}else{
self.rotateRight.hidden = YES;
self.rotateLeft.hidden = YES;
self.rollToL.hidden = YES;
self.rollToR.hidden = YES;
self.pitchToDown.hidden = YES;
self.pitchToUp.hidden = YES;
self.rollDot.hidden = NO;
self.pitchDot .hidden =NO;
self.yawDot.hidden = NO;
[self.yawDot setFrame:CGRectMake(0, 390, 20, 20)];
[self.rollDot setFrame:CGRectMake(0, 195, 20, 20)];
[self.pitchDot setFrame:CGRectMake(150, 0, 20, 20)];
}
}];
Please let me know if any further details are needed on this.
Any suggestions or advice is always welcome, :) I am a noob to programming and to ios.
Thanks!!
I think the following things are necessary to manage to task:
First of all you need a good understanding of quaternions (skip this if you already made friends with them). I recommend OpenGL:Tutorials:Using Quaternions to represent rotation or The Matrix and Quaternions FAQ. It helps to bear in mind that (x, y, z) represent the axis to rotate around (not normalised) and w = cos (alpha/2) i.e. stands approximately for the amount of rotation.
As CMQuaternion is just a struct thus it's hard to do all the calculations. Use a full functional quaternion class instead like cocoamath (you need at least Quaternion.h, .m and QuaternionOperation.m from trunk).
Now the basic considerations:
The difference (or sometimes stated as division) between two quaternions being defined as the angular displacement from one orientation to another might be the way to go. It is defined as
d = a-1 * b
So this expresses the delta from the current position to the target position.
Having this delta you need to define the conditions to met for considering the target orientation as reached. My first idea is to use the delta angle. This can be easily retrieved from the w component of the above calculated d quaternion by:
alpha = 2 * arccos (w)
The domain of arccos is restricted but this shouldn't be a problem in this case as we are especially interested in small values.
Maybe it's worth to emphasize that every 3D rotation has two unit quaternion representations, q and -q. This might be confusing but doesn't matter.
Update:
So a bit of pseudo code would look something like:
CMQuaternion cmQ = attitude.quaternion;
// Get an instance of cocoamath's Quaternion for our currently reported quaternion
Quaternion current = [Quaternion initWithRe:(double)cmQ.w i:(double)cmQ.x j:(double)cmQ.y k:(double)cmQ.z];
// the complex conjugate and normalised to be on the safe side
Quaternion inverse = [current inverse];
// build the delta, assuming you have your stored direction as class member targetQuaternion
Quaternion diff = [inverse multiply:targetQuaternion];
float alpha = 2 * acos (diff.Re);
// maxDeltaAngle is your class member variable defining the angle from which you assume the position as restored (radians)
if (fabs (alpha) < maxDeltaAngle) {
// do my fancy camera things
}
Yes it's not that trivial at the beginning. As Ali stated in his answer, visualisation is an important issue as well. My solution just solves the raw maths part.
I am not saying that the following is the solution to your problem, I have no idea how user-friendly this will be but I believe it is worth a shot. I see two issues: how you store the attitude and how you visualize them.
Storage. I would save the attitude either as a quaternion or as a rotation matrix; CMAttitude provides both. (I personally prefer rotation matrices to quaternions as rotation matrices are easier to understand in my opinion. It is just a personal preference.)
Visualization. You are using 3 markers to bring the phone to the same attitude as the saved one. These 3 markers came from yaw, pitch and roll, effectively ruining the stability of your application. Instead of these 3 markers I would visualize the rotation between the saved attitude and the current one. One way of doing it is to project the rotated 3 basis vectors onto the phone's screen. The nice thing about rotation matrices is that they give you exactly this without any extra computation. For example, to visualize the
| 1 0 0 |
| 0 1 0 |
| 0 0 1 |
rotation this rotation matrix represents, I only need to plot 3 arrows from [0, 0, 0] to (i) [1, 0, 0], (ii) [0, 1, 0] and (iii) [0, 0, 1]. That is, I only need to plot either the rows or the columns of the matrix (also depends on what you want whether the rows or the columns are better).
To get the rotation between a saved rotation matrix S and the current rotation matrix C you need to compute CTS (in words: C transpose times S). You have aligned the phone with the saved attitude if your rotation matrix is the one shown above.
An excellent tutorial on rotation matrices is the Direction Cosine Matrix IMU: Theory manuscript.
Another way of visualizing the rotation between the saved and the current attitude is to transform it to angle-axis form. Then, I would project the axis to the phone's screen. The user first aligns the phone with the axis, than needs to rotate around the axis to match the saved attitude. Quaternions basically represent the axis-angle form, although in a nontrivial way.
It requires significant further work on your part to alter this idea according to your needs; this is definitely not a complete answer. Nevertheless, I hope this help a bit.
Related
buttonTapped method check if it is an small iphone, normal iphone or ipad. Set the image name and size accordingly. There is no problem with it, works as expected.
When clicked on the "pressedClassicButton" it reloads some data in other views and changes images of some other uiviews. it should not touch imageView in any way. The data it changes are not connected to imageView also.
But somehow when i call the "pressedClassicButton" method, it starts ignoring my specified ipad frame sizes and sets the imageView size as specified on the storyboard constraints. This problem only happens in ipad.
The constraints are: 1:1 ratio, 20px to left right and top.
- (IBAction)pressedClassicButton:(id)sender {
[Flurry logEvent:#"Pressed->SelectPage->Classic"];
[self toggleButton:sender];
self.currentArray = self.classicArray;
[self.swipeView reloadData];
}
-(void)toggleButton:(UIButton *)button{
UIImage *inactiveButton = [UIImage imageNamed:#"fruitify_tab_inactive.png"];
UIImage *activeButton = [UIImage imageNamed:#"fruitify_tab_active.png"];
[self.buttonClassicOut setBackgroundImage:inactiveButton forState:UIControlStateNormal];
[self.buttonGroupOut setBackgroundImage:inactiveButton forState:UIControlStateNormal];
[self.buttonSpecialOut setBackgroundImage:inactiveButton forState:UIControlStateNormal];
[self.buttonTropicalOut setBackgroundImage:inactiveButton forState:UIControlStateNormal];
[button setBackgroundImage:activeButton forState:UIControlStateNormal];
}
- (void)buttonTapped:(UIButton *)sender
{
int clicked = (int)sender.tag;
self.lastChoice = clicked;
NSString *str = self.currentArray[clicked][3];
for (UIView *subView in self.imageView.subviews)
{
if (subView.tag < 120 && subView.tag > 100)
{
[subView removeFromSuperview];
}
}
NSString *machineName = [[NSString alloc] initWithString:[MasterClass MachineName]];
if ([machineName rangeOfString:#"iPad"].location != NSNotFound) {
NSLog(#"ipad");
self.imageView.frame = CGRectMake(120.0, 80.0, 520, 520);
} else if ([machineName rangeOfString:#"iPhone3"].location != NSNotFound) {
self.imageView.frame = CGRectMake(50.0, 70.0, 220, 220);
NSLog(#"iphone3-4");
} else if ([machineName rangeOfString:#"iPod"].location != NSNotFound) {
self.imageView.frame = CGRectMake(50.0, 70.0, 220, 220);;
NSLog(#"iphone3-4");
}
else{
self.imageView.frame = CGRectMake(20.0, 82.0, 280, 280);
NSLog(#"iphone5+");
}
self.imageView.image = [UIImage imageNamed:self.currentArray[clicked][4]];
[self.imageView setUserInteractionEnabled:YES];
float realImageSize = self.imageView.image.size.height;
float viewBoundSize = self.imageView.bounds.size.height;
float sizeDifference = realImageSize / viewBoundSize;
if (![str isEqual: #"group"]) {
CGPoint eye = CGPointMake([self.currentArray[clicked][8] floatValue] / sizeDifference, [self.currentArray[clicked][9] floatValue] / sizeDifference);
CGPoint mouth = CGPointMake([self.currentArray[clicked][10] floatValue] / sizeDifference, [self.currentArray[clicked][11] floatValue] / sizeDifference);
CGPoint center = CGPointMake( (eye.x + mouth.x)/2 , (eye.y + mouth.y)/2 );
CGFloat xDist = (eye.x - mouth.x);
CGFloat yDist = (eye.y - mouth.y);
CGFloat faceSize = sqrt((xDist * xDist) + (yDist * yDist));
UIImage *tapFace = [UIImage imageNamed:#"fruitify_face_button.png"];
CGRect newBound = CGRectMake(center.x - faceSize, center.y - faceSize, faceSize*2, faceSize*2);
UIButton *tapButton = (UIButton *)self.imageView;
tapButton = [UIButton buttonWithType:UIButtonTypeCustom];
[tapButton setFrame:newBound];
[tapButton setBackgroundImage:tapFace forState:UIControlStateNormal];
tapButton.userInteractionEnabled = YES;
tapButton.tag = 113;
tapButton.enabled = YES;
[tapButton addTarget:self action:#selector(tapHereTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.imageView addSubview:tapButton];
}else if([str isEqual:#"group"]){
int faceCount = [self.currentArray[clicked][8] count];
for (int i = 0; i < faceCount; i++) {
CGPoint eye = CGPointMake([self.currentArray[clicked][8][i] floatValue] / sizeDifference, [self.currentArray[clicked][9][i] floatValue] / sizeDifference);
CGPoint mouth = CGPointMake([self.currentArray[clicked][10][i] floatValue] / sizeDifference, [self.currentArray[clicked][11][i] floatValue] / sizeDifference);
CGPoint center = CGPointMake( (eye.x + mouth.x)/2 , (eye.y + mouth.y)/2 );
CGFloat xDist = (eye.x - mouth.x);
CGFloat yDist = (eye.y - mouth.y);
CGFloat faceSize = sqrt((xDist * xDist) + (yDist * yDist));
UIImage *tapFace = [UIImage imageNamed:#"fruitify_face_button.png"];
CGRect newBound = CGRectMake(center.x - faceSize, center.y - faceSize, faceSize*2, faceSize*2);
UIButton *tapButton = (UIButton *)self.imageView;
tapButton = [UIButton buttonWithType:UIButtonTypeCustom];
[tapButton setFrame:newBound];
[tapButton setBackgroundImage:tapFace forState:UIControlStateNormal];
tapButton.userInteractionEnabled = YES;
tapButton.tag = 113 + i;
tapButton.enabled = YES;
[tapButton addTarget:self action:#selector(tapHereTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.imageView addSubview:tapButton];
}
}
}
Unexpected things happen when you use constraints and try to set frames as well. Try setting the sizes of your views by altering their constraints instead. You add them as properties from your storyboard by ctrl click drag like you do when hooking up a button.
NSMutableArray *views = [[NSMutableArray alloc]initWithCapacity:0];
for (NSInteger i = 0; i<16; i++)
{
UIView *circle = [[UIView alloc]init];
circle.backgroundColor = [UIColor clearColor];
UIImageView *circleImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 80, 80)];
circleImage.image = [UIImage imageNamed:#"circle"];
[circle addSubview:circleImage];
UILabel *labelInsideCircle = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 40, 40)];
labelInsideCircle.backgroundColor = [UIColor clearColor];
labelInsideCircle.textColor = [UIColor greenColor];
labelInsideCircle.font = [UIFont fontWithName:#"Helvetica" size:30.0];
labelInsideCircle.center = circleImage.center;
NSInteger int_ = [self getRandomNumber:0 to:(arrOfOptions.count-1)];
labelInsideCircle.text = [NSString stringWithFormat:#"%#",[arrOfOptions objectAtIndex:int_]];
labelInsideCircle.textAlignment = NSTextAlignmentCenter;
[arrOfOptions removeObjectAtIndex:int_];
[circle addSubview:labelInsideCircle];
[labelInsideCircle release];
[views addObject:circle];
[circle release];
[circleImage release];
}
/* Rotating circles with angles */
float curAngle = 0;
float incAngle = ( 360.0/(views.count) )*3.14/180.0;
CGPoint circleCenter = CGPointMake(380, 580); /* given center */
float circleRadius = 250; /* given radius */
for (UIView *view in views)
{
CGPoint viewCenter;
viewCenter.x = circleCenter.x + cos(curAngle)*circleRadius;
viewCenter.y = circleCenter.y + sin(curAngle)*circleRadius;
view.transform = CGAffineTransformRotate(view.transform, curAngle);
view.center = viewCenter;
[self.view addSubview:view];
curAngle += incAngle;
}
The problem is here the text of UILabel is also getting transformed, which is obvious. What I want is 16 circular views with labels on them without the label's text transformed. Can anyone please help me out with this ?
In this case, you just need to change their location coordinates, not rotate them.
NSMutableArray *views = [[NSMutableArray alloc] initWithCapacity:0];
for (NSInteger i = 0; i<16; i++)
{
UIView *circle = [[UIView alloc]init];
circle.backgroundColor = [UIColor clearColor];
UIImageView *circleImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 80, 80)];
circleImage.image = [UIImage imageNamed:#"circle"];
[circle addSubview:circleImage];
UILabel *labelInsideCircle = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 40, 40)];
labelInsideCircle.backgroundColor = [UIColor clearColor];
labelInsideCircle.textColor = [UIColor greenColor];
labelInsideCircle.font = [UIFont fontWithName:#"Helvetica" size:30.0];
labelInsideCircle.center = circleImage.center;
NSInteger int_ = arc4random()%[arrOfOptions count];
labelInsideCircle.text = [NSString stringWithFormat:#"%#",[arrOfOptions objectAtIndex:int_]];
labelInsideCircle.textAlignment = NSTextAlignmentCenter;
[arrOfOptions removeObjectAtIndex:int_];
[circle addSubview:labelInsideCircle];
[labelInsideCircle release];
[views addObject:circle];
[self.view addSubview:circle];
[circle release];
[circleImage release];
}
/* Rotating circles with angles */
float curAngle = 0;
float incAngle = ( 360.0/(views.count) )*3.14/180.0;
CGPoint circleCenter = CGPointMake(380, 580); /* given center */
float circleRadius = 250; /* given radius */
for (UIView *view in views)
{
CGPoint viewCenter;
viewCenter.x = circleCenter.x + cos(curAngle)*circleRadius;
viewCenter.y = circleCenter.y + sin(curAngle)*circleRadius;
//view.transform = CGAffineTransformRotate(view.transform, curAngle);
view.center = viewCenter;
[self.view addSubview:view];
curAngle += incAngle;
}
Some coding suggestions:
You can use arc4random() function if you don't have anything special in your own random number generator.
You can turn on ARC in xCode!
In a loop set their centers accordingly, don't rotate them nor their super view.
The following is not directly related to your question but may be helpful:
Aparaently you have got all the math available. I suggest to measure how much cpu time gets lost on the cos and sin etc. calls. If you find that significant then think about your algorithm. You will (most probably) find out that you call cos and sin hundrets or thousands of times for a limited number of angles.
You may then try it and find out that possible pre-calculations or just "caching and reusing" earlier results may save significant processing time.
Plus pre-calculating or caching of sinus would do. You can derrive cosinus values from sinus (and vice versa) by adding (or substracting respectively) an offset of pi/2 (or 90 degrees respectively) to the argument.
For similar tasks I was working with degrees (not radiants) and found out that I could not predict the angles but that a significant of full degrees (1°, 2°, 3°, ... and nothing in between) was exact enough for the job. Then I maintained an array of 360 sinus values and used that instead of calling the real function again and again. (Plus I only had to calculate 90 of them and mirrored the results of the other 270 degrees according to the nature of the sinus function) 180 floats is not too much of memory compared to the speed that I gained. Something like that can be suitable for you too. In your case your potenital agruments to sin and cos are limited to full-number multipilers of incAngle.
Which means there are only [views count] number of potential values each.
I am developing one application same as HairTryOn all things are done. but problem is display in following image. i want to set hair style as per customer face using blue line as per display in image.
i used the following code
testVw = [[UIView alloc]initWithFrame:CGRectMake(100,100, 100, 100)];
testVw.backgroundColor = [UIColor clearColor];
[self.view addSubview:testVw];
resizeVw = [[UIImageView alloc]initWithFrame:CGRectMake(testVw.frame.size.width-25, testVw.frame.size.height-25, 25, 25)];
resizeVw.backgroundColor = [UIColor clearColor];
resizeVw.userInteractionEnabled = YES;
resizeVw.image = [UIImage imageNamed:#"button_02.png" ];
[testVw addSubview:resizeVw];
UIPanGestureRecognizer* panResizeGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(resizeTranslate:)];
[testVw addGestureRecognizer:panResizeGesture];
The resizeTranslate: method:
-(void)resizeTranslate:(UIPanGestureRecognizer *)recognizer
{
if ([recognizer state]== UIGestureRecognizerStateBegan)
{
prevPoint = [recognizer locationInView:testVw.superview];
[testVw setNeedsDisplay];
}
else if ([recognizer state] == UIGestureRecognizerStateChanged)
{
if (testVw.bounds.size.width < 20)
{
testVw.bounds = CGRectMake(testVw.bounds.origin.x, testVw.bounds.origin.y, 20,testVw.bounds.size.height);
imgvw.frame = CGRectMake(12, 12, testVw.bounds.size.width-24, testVw.bounds.size.height-27);
resizeVw.frame =CGRectMake(testVw.bounds.size.width-25, testVw.bounds.size.height-25, 25, 25);
rotateVw.frame = CGRectMake(0, testVw.bounds.size.height-25, 25, 25);
closeVw.frame = CGRectMake(0, 0, 25, 25);
}
if(testVw.bounds.size.height < 20)
{
testVw.bounds = CGRectMake(testVw.bounds.origin.x, testVw.bounds.origin.y, testVw.bounds.size.width, 20);
imgvw.frame = CGRectMake(12, 12, testVw.bounds.size.width-24, testVw.bounds.size.height-27);
resizeVw.frame =CGRectMake(testVw.bounds.size.width-25, testVw.bounds.size.height-25, 25, 25);
rotateVw.frame = CGRectMake(0, testVw.bounds.size.height-25, 25, 25);
closeVw.frame = CGRectMake(0, 0, 25, 25);
}
CGPoint point = [recognizer locationInView:testVw.superview];
float wChange = 0.0, hChange = 0.0;
wChange = (point.x - prevPoint.x); //Slow down increment
hChange = (point.y - prevPoint.y); //Slow down increment
testVw.bounds = CGRectMake(testVw.bounds.origin.x, testVw.bounds.origin.y, testVw.bounds.size.width + (wChange), testVw.bounds.size.height + (hChange));
imgvw.frame = CGRectMake(12, 12, testVw.bounds.size.width-24, testVw.bounds.size.height-27);
resizeVw.frame =CGRectMake(testVw.bounds.size.width-25, testVw.bounds.size.height-25, 25, 25);
rotateVw.frame = CGRectMake(0, testVw.bounds.size.height-25, 25, 25);
closeVw.frame = CGRectMake(0, 0, 25, 25);
prevPoint = [recognizer locationInView:testVw.superview];
[testVw setNeedsDisplay];
}
else if ([recognizer state] == UIGestureRecognizerStateEnded)
{
prevPoint = [recognizer locationInView:testVw.superview];
[testVw setNeedsDisplay];
}
}
This code Resizing full view. but i want to Resize only those part which is moved by finger.
I don't see how your primitives are constructed. But you will need to partition your point set according to touch zones and scale then as needed. If you convert your lines into Bezier curves, then manipulating the curve will be easier, as you will not need to do terrible amount of adjustment to the shape with a small modifications.
Im showing some data in a simple bar that charts some values, all is working, but now to build the scale for my Y axis, im having some problems with some basic stuff,
I get the ceiling value for my chart with scaleMaxInt in this case for testing = 900
then according to some simple test, I get for example 9 divisions [plus zero]...
so to make the thing work, I create the int pp=100
- (void) drawScaleLabels:(int)scaleMaxInt
{
//temp division for scale, NOTE WHERE TO USE 9 DIVISIONS!!
int scaleStep = scaleMaxInt/9;
NSLog(#"va ::%d", scaleStep);
// case scaleMax : 0 < scaleMax < 1000
float scaleDiv = 31.5;
for (int i = 0; i<9; i++)
{ // is 8, to 9 only for testing!!
int pp = 100;
self.divScaleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(469, scaleDiv+286, 60, 14)]autorelease];
self.divScaleLabel.font = [UIFont fontWithName:#"FS Albert" size:14];
self.divScaleLabel.textColor = [UIColor whiteColor];
self.divScaleLabel.textAlignment = UITextAlignmentRight;
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d",scaleMaxInt-scaleStep];
self.divScaleLabel.backgroundColor =[UIColor clearColor];
[self.view addSubview:self.divScaleLabel];
scaleDiv = scaleDiv + 31.5;
scaleStep = scaleStep+pp;
}
}
wich shows the list of values in my Y axis: 900 , 800 , 700 ,600... 0
but if I use the result of the int scaleStep = scaleMaxInt/9
it gives me the list but with the value*2
- (void) drawScaleLabels:(int)scaleMaxInt
{
//temp division for scale, NOTE WHERE TO USE 9 DIVISIONS!!
int scaleStep = scaleMaxInt/9;
NSLog(#"va ::%d", scaleStep);
// case scaleMax : 0 < scaleMax < 1000
float scaleDiv = 31.5;
for (int i = 0; i<9; i++)
{ // is 8, to 9 only for testing!!
//int pp = 100;
self.divScaleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(469, scaleDiv+286, 60, 14)]autorelease];
self.divScaleLabel.font = [UIFont fontWithName:#"FS Albert" size:14];
self.divScaleLabel.textColor = [UIColor whiteColor];
self.divScaleLabel.textAlignment = UITextAlignmentRight;
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d",scaleMaxInt-scaleStep];
self.divScaleLabel.backgroundColor =[UIColor clearColor];
[self.view addSubview:self.divScaleLabel];
scaleDiv = scaleDiv + 31.5;
NSLog(#"va ::%d", scaleStep);
scaleStep = scaleStep+scaleStep;
}
}
so Y axis is, 900, 800 , 700, 500 , 100, -700 ...-24700
Im stupidly stuck with this!,
how can i generate the list for Y axis?, with a dynamic value, dependent of scaleMaxInt
that doesn't get confused??
thanks a lot!
You have this in your loop:
scaleStep = scaleStep+scaleStep;
That means you are doubling scaleStep on each pass through the loop. Remove that line. Instead, set self.divScaleLabel.text like this:
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d", scaleMaxInt - scaleStep * i];
I can't seem to find any documentation online about this, and what I am googling is giving me a lot of conflicting information...
From iphonedevsdk.com:
The accelerometers used in the first
and second generation iPhones (and I
assume also the iPod touches) operate
in two modes: +/- 2 g, and +/- 8 g.
(Actually, as you observed, they
report accelerations somewhat outside
the nominal range. Accuracy is not
spec'ed outside that range, though.)
Apple operates them in the +/- 2 g
mode. There is a tradeoff: The current
resolution is nominally 0.018 g,
according to the datasheet (though my
first generation iPhone uses
0.018168604, according to a modified version of AccelerometerGraph). In the
+/- 8 g mode, the resolution would be four times cruder.
I assume Apple decided that the finer
resolution would be more useful than
the wider range. (I'd rather see finer
resolution than 0.018 g. So neither of
us is fully satisfied.)
You cannot change the mode with any
published feature of the APIs. Since
they are passing acceleration as a
double, they could theoretically allow
a change in mode, and simply look at
the mode when rescaling the A/D value,
before reporting acceleration. (The
obvious place to set the mode would be
in the call which sets up the
application to receive accelerometer
information.) However, for backward
compatibility, the OS would have to
set the accelerometer mode to +/- 2 g
at the beginning of the application.
And none of the background processes
could be allowed to set the
accelerometer mode.
The aGauge app displays this raw data coming from the accelerometer as you move the device. It can also help you find the "flip" threshold for your device.
I created the following application to try to test out the ranges...
UIAccelerometer *objAccelerometer;
UILabel *lblxmin, *lblxmax, *lblymin, *lblymax, *lblzmin, *lblzmax;
UILabel *lblxnow, *lblynow, *lblznow;
float xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0, zmin = 0.0, zmax = 0.0, xnow = 0.0, ynow = 0.0, znow = 0.0;
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
//NSLog (#"%f, %f, %f", acceleration.x, acceleration.y, acceleration.z);
xnow = acceleration.x;
ynow = acceleration.y;
znow = acceleration.z;
if (xnow < xmin) { xmin = xnow; }
if (ynow < ymin) { ymin = ynow; }
if (znow < zmin) { zmin = znow; }
if (xnow > xmax) { xmax = xnow; }
if (ynow > ymax) { ymax = ynow; }
if (znow > zmax) { zmax = znow; }
lblxmin.text = [NSString stringWithFormat:#"%f", xmin];
lblymin.text = [NSString stringWithFormat:#"%f", ymin];
lblzmin.text = [NSString stringWithFormat:#"%f", zmin];
lblxmax.text = [NSString stringWithFormat:#"%f", xmax];
lblymax.text = [NSString stringWithFormat:#"%f", ymax];
lblzmax.text = [NSString stringWithFormat:#"%f", zmax];
lblxnow.text = [NSString stringWithFormat:#"%f", xnow];
lblynow.text = [NSString stringWithFormat:#"%f", ynow];
lblznow.text = [NSString stringWithFormat:#"%f", znow];
}
-(void) invokeAccelerometer {
objAccelerometer = [UIAccelerometer sharedAccelerometer];
objAccelerometer.delegate = self;
objAccelerometer.updateInterval = (1.0 / 10.0);
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
lblxmin = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 40)];
lblxnow = [[UILabel alloc] initWithFrame:CGRectMake(100, 10, 100, 40)];
lblxmax = [[UILabel alloc] initWithFrame:CGRectMake(200, 10, 100, 40)];
lblymin = [[UILabel alloc] initWithFrame:CGRectMake(10, 60, 100, 40)];
lblynow = [[UILabel alloc] initWithFrame:CGRectMake(100, 60, 100, 40)];
lblymax = [[UILabel alloc] initWithFrame:CGRectMake(200, 60, 100, 40)];
lblzmin = [[UILabel alloc] initWithFrame:CGRectMake(10, 110, 100, 40)];
lblznow = [[UILabel alloc] initWithFrame:CGRectMake(100, 110, 100, 40)];
lblzmax = [[UILabel alloc] initWithFrame:CGRectMake(200, 110, 100, 40)];
[self.view addSubview:lblxmin];
[self.view addSubview:lblxnow];
[self.view addSubview:lblxmax];
[self.view addSubview:lblymin];
[self.view addSubview:lblynow];
[self.view addSubview:lblymax];
[self.view addSubview:lblzmin];
[self.view addSubview:lblznow];
[self.view addSubview:lblzmax];
[self invokeAccelerometer];
[super viewDidLoad];
}
The problem is, when I rotate my iPod along the horizontal/vertical axes and then flip it over, I get values like:
xmin -1.271802
xmax 1.180959
ymin -1.344477
ymax 1.108285
zmin -2.30713
zmax 2.325581
If I take the iPod and shake the heck out of it, I get...
x -2.325581 to 2.307413
y -2.325581 to 2.307413
z -2.307413 to 2.325581
Any ideas what it's measuring?
The best I've come up with is:
vertical axis
x = -1 if tilted to the left ( <| )
x = +1 if tilted all the way to the right ( |> )
where < is the way the screen faces, and | is the bottom of the iPod
y ~ -1 if screen is facing you, perpendicular to floor ("standing up")
y ~ 1 if facing away from you (and upside down)