CGGradient: Drawing a linear gradient on an angle - iphone

I am trying to draw a linear CGGradient on an angle. Because "CGContextDrawLinearGradientWithAngle()" does not exist, I am trying to use CGContextDrawLinearGradient(CGContextRef, CGGradientRef, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions).
With that in mind, I need to convert an angle (degrees) into a starting point and an ending point. I would like to mimic NSGradient's drawInBezierPath:angle. (As a part of AppKit, NSGradient is sadly not available to iOS developers.) Fortunately, the documentation tells us how to get the starting gradient:
- (CGPoint)startingPointForAngle:(CGFloat)angle rect:(CGRect)rect {
CGPoint point = CGPointZero;
if (angle < 90.0f)
point = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
else if (angle < 180.0f)
point = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
else if (angle < 270.0f)
point = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
else
point = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
return point;
}
Unfortunately, the documentation does not tell us how to get the ending point. (Using either the height or the width of the rect as the distance is only sufficient for certain angles.) Several sites out there tells us how we can find the ending point. Unfortunately, the distance needs to be known before I can compute the ending point. Yet the ending point needs to be computed to get the distance. There is clearly more to it, as NSGradient seems to have it figured out.
- (CGPoint)endingPointForAngle:(CGFloat)angle rect:(CGRect)rect startingPoint:(CGPoint)startingPoint {
//http://www.zahniser.net/~russell/computer/index.php?title=Angle%20and%20Coordinates
//(x + distance * cos(a), y + distance * sin(a))
CGFloat angleInRadians = (CGFloat)M_PI/180.0f * angle;
CGFloat distance = ????????;
CGPoint point = CGPointMake(startingPoint.x + distance * cosf(angleInRadians), startingPoint.y + distance * sinf(angleInRadians));
return point;
}
CGPoint startingGradientPoint = [self startingPointForAngle:self.fillGradientAngle rect:rect];
CGPoint endingGradientPoint = [self endingPointForAngle:self.fillGradientAngle rect:rect startingPoint:startingGradientPoint];
CGContextDrawLinearGradient(graphicsContext, self.fillGradient, startingGradientPoint, endingGradientPoint, 0);
Any ideas.

I'm dealing with the same problem, with a little different way, I use the center point and and angle, and extend the side left and right to find it's points on the edge, my problem was there will be white space if the angel is not pointing any axis, and the drawing option the functions provide a "kCGGradientDrawsBeforeStartLocation or kCGGradientDrawsAfterEndLocation" so it looks like one side will be empty.
But it turns out I can combine the two options with '|', so problem solved, here's my code:
CGGradientRef grRef = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradient.colorArray, NULL);
CGFloat degree = angle * M_PI / 180;
CGPoint center = CGPointMake(width/2, height/2);
CGPoint startPoint = CGPointMake(center.x - cos (degree) * width/2, center.y - sin(degree) * height/2);
CGPoint endPoint = CGPointMake(center.x + cos (degree) * width/2, center.y + sin(degree) * height/2);
NSLog(#"start point:%# \n, end point: %#",NSStringFromCGPoint(startPoint),NSStringFromCGPoint(endPoint));
CGContextDrawLinearGradient(gradientContext, grRef, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);

I'm not 100% sure how this gradient thing works but from what you've written I'm assuming that you basically just want the length of a line from the starting point until it hits the side of the rectangle.
If this is the case you simply need to do some trigonometry. Lets call the distance x and the angle a.
Between 0 and 45 degrees: width = xcos(a) so x = width/cos(a)
Between 45 and 90 degress: height = xsin(a) so x = height/sin(a)
Between 90 and 135 degrees we have moved to a new corner. Here x = height/cos(a-90).
Between 135 and 180 x = width/sin(a-90)
Between 180 and 225 we have again moved corner. Here x = width/cos(a-180).
Between 225 and 270 x = height/sin(a-180)
Last corner! Between 270 and 315 x = height/sin(a-270)
And finally between 315 and 360 x = width/cos(a-270)
Some of these probably simplify but its easiest to think about the line starting in the bottom left corner pointing right and sweeping round anticlockwise which is what appears to happen in your starting point calculation.

Related

Get x,y point from angle and radius

I need to get a specific point with specified angle and distance(Radius) from center point of my base UIView. I found this question How to calculate a point with an given center, angle and radius? but this doesn't give me correct point. I'm using below code to find the point,
Basically i need to find the x,y point from 100px distance and 90 degrees(clock wise) from center point of my base view.
int angle = 90 * M_PI / 180;//need to find the point of 90 degrees
int distance = 100;
int line_end_x = 160 + cos(angle)*distance;//160 is my center x
int line_end_y = 274 + sin(angle)*distance;//274 is my center y
myView.center = CGPointMake(line_end_x, line_end_y);
Above code gives me below output,
Red box is "myView" with (20px x 20px)
May be someone can tell me whats wrong i'm doing?
I doubt that you want your angle to be expressed as an int.
Your angle is Pi/2 which is 1.57.
Maybe try float angle = 90 * M_PI / 180;

How to calculate the slope between touch point and UIImageView center

How to calculate the slope between touch point and UIImageView center?
I tried a lot of codes, one of them is this:
CGPoint translation = [gesture translationInView:[myImage superview]];
double tx = myImage.center.x - translation.x;
double ty = myImage.center.y - translation.y;
double t_length = sqrt((tx*tx) + (ty*ty));
double a = cos(ty / t_length);
It is not giving a right angle, so please help me
atan2(y, x) returns the angle between the line from (0, 0) to (x, y) and the positive
x-axis, so you probably need
double angle = atan2(myImage.center.y - translation.y, myImage.center.x - translation.x);
The return value is in radians, between -Pi and Pi. Note that the "y-value" comes first!
For this question, you want to think about which point you want the slope relative to. Actually, the magnitude of the slope will be the same but the signs would change. For example if the situation is like the image below:
then you would calculate your slope using:
CGPoint myImageCenter = myUIImage.center;
CGPoint myClickedPoint =[gesture translationInView:[myImage superview]];
double slope = (myImageCenter.y - myClickedPoint.y) / ((myImageCenter.x - myClickedPoint.x))
This slope is the slope of the line, but be sure to think about touches that may happen in the top right corner, exactly above or below and exactly on the same horizontal plane as the center.

Projectile Motion in Cocos2d iphone

I want to throw a ball that has a projectile motion. I have a monkey on centre of screen and onTouchBegin I am taking the starting point of the touch and onTouchEnded I am taking the ending points. From the starting and ending points I am taking the angle value between them. Like 30 degrees, 45 or 90 degree.
This is my code by which I have calculated angle of start to endpoint
float angleRadians = atan2(startTouchPoint.x - touchPoint.x, startTouchPoint.y - touchPoint.y);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
Now i am using Projectile motion formula to throw ball with angle i have calculated from above formula .
inside init method
gravity = 9.8; // metres per second square
X = 0;
Y = 0;
V0 = 50; // meters per second -- elevation
VX0 = V0 * cos(angle); // meters per second
VY0 = V0 * sin(angle); // meters per second
gameTime = 0;
and onTouchEnded i have called fire method which will throw ball .
-(void)fire:(ccTime) dt
{
CCLOG(#"Angle 1: %.2f",angle);
gameTime += dt*6;
// x = v0 * t * cos(angle)
X = (V0 * gameTime * cos(angle))/2+120;
// y = v0 * t * sin(angle) - 0.5 * g * t^2
Y = (V0 * gameTime * sin(angle) - 0.5 * gravity * pow(gameTime, 2))/2+255;
if (Y > 50)
{
sprite_webfire.position = ccp(X,Y);
flag = true;
}
else
{
//angleValue += 15;
angleValue = angle;
angle = [self DegreesToRadians:angleValue];
gravity = 9.8; // metres per second square
X = 0;
Y = 0;
V0 = 50; // meters per second -- elevation
VX0 = V0 * cos(angle); // meters per second
VY0 = V0 * sin(angle); // meters per second
gameTime = 0;
// [self pauseSchedulerAndActions];
}
if (Y < 50)
{
[self unschedule:#selector(fire:)];
}
NSLog(#"ball (%lf,%lf), dt = %lf angle value %d", X, Y, dt,angleValue);
}
this code is working . by this code i can throw ball in projectile motion but i cant throw it where i want to. i cant throw wrt to given angle from start to end point.
i can throw it like red mark but i want to throw it blue mark with swipe . but its not throwing like i am swiping screen.
I am not certain on what math you are using to do this, I find your documentation a bit confusing.
Generally, for project tile motion this is what you need to do:
Find out what the take off angle is relative to the horizontal. Then depending on whatever initial velocity you want the object to have, use that and you trig equations to put your initial velocities into rectangular components.
For example:
If initial velocity was 10, the initial velocity in the y direction would be 10sin(angle), and in the x direction it would be 10cos(angle).
Then in to update the position of the sprite you should use kinematics equations: http://www.physicsclassroom.com/class/1dkin/u1l6c.cfm
First update velocities:
Velocity in the Y direction: V = v(initial) + gravity*(Delta-time)
Velocity in the X direction is constant unless you want to factor in some sort of resistance to make things a lot more complicated.
then position y = oldPositionY + velocity(in Y direction)*(Delta-time) + 1/2(gravity)(delta-time)^2.
and position x = oldPositionX + Xvelocity*delta-time
I have done some projectile motion stuff, and I have found you need to make gravity a large constant, something around 500 to make it look life-like. Let me know if this is confusing or you don't know how to implement it.
I would suggest that you take a look at the following tutorial: http://www.raywenderlich.com/4756/how-to-make-a-catapult-shooting-game-with-cocos2d-and-box2d-part-1.
It shows you how to use a physics engine, so you don't need to do much of the math. All the 'bullets' in the tutorial are also moving with projectile motion.
I'll add a bit to what was already said (which was good). Firstly, you should not be wasting time computing any angles. Stick with vectors for your velocity. In other words, get the initial velocity vector from the touch start and end location, and that will be your (v0x, v0y). For example:
CGPoint initialVelocity = ccpSub(touchPoint, startTouchPoint);
float v0x = initialVelocity.x;
float v0y = initialVelocity.y;
If you wish to assign a different magnitude to the initial velocity vector, simply normalize it and then multiply it by a new magnitude.
CGPoint unitVelocity = ccpNormalize(initialVelocity);
float magnitude = 200; // or whatever you want it to be
CGPoint velocity = ccpMult(unitVelocity, magnitude);
Anyway, with this velocity set properly you can then use it in your position calculations as before, but without the added complexity of calculating the angles.
-(void) fire:(ccTime)dt
{
.
.
gameTime += dt;
// if x(t) = x0 + v0x*t, then dx = v0x*dt
x += v0x*dt;
// if y(t) = y0 + v0y*t - 0.5t^2, then dy = v0y*dt - g*t*dt
y += (v0y * dt - g*gameTime*dt);
.
.
}
Also you should not set v0 = 50. Calculate the velocity from the vector as I suggested.
Something important to consider is that you are calculating what the movement should be in a physical world based upon units of meters. The screen is operating in points, not meters, so you will probably have to apply a scaling factor to the new position (x,y) to get the look that you are going for.
Edit: my bad, I had to revisit my math in the position calculation. My differentials was a bit rusty.

Game programming difficult mathematical issue

The question I am about to ask could be somewhat challenging. I will try to make this as clear and cohesive as possible.
I am currently making a game, in which I have a 'laser ring,' as shown here:
This laser ring, when prompted, will fire a 'grappling hook' which is simply the image shown below. This image's frame.width property is adjusted to make it fire (lengthen) and retract (shorten.) It starts at a width of 0, and as the frames progress, it lengthens until reaching the desired point.
This grappling hook, when fired, should line up with the ring so that they appear to be one item. Refer to the image below for clarity:
*Note that the grappling hook's width changes almost every frame, so a constant width cannot be assumed.
Something else to note is that, for reasons that are difficult to explain, I can only access the frame.center property of the grappling hook and not the frame.origin property.
So, my question to you all is this: How can I, accessing only the frame.center.x and frame.center.y properties of the grappling hook, place it around the laser ring in such a way that it appears to be seamlessly extending from the ring as shown in the above image - presumably calculated based on the angle and width of the grappling hook at any given frame?
Any help is immensely appreciated.
OK, I've done this exact same thing in my own app.
The trick I did to make it easier was to have a function to calculate the "unitVector" of the line.
i.e. the vector change in the line based on a line length of 1.
It just uses simple pythagorus...
- (CGSize)unitVectorFromPoint:(CGPoint)start toPoint:(CGPoint)end
{
//distance between start an end
float dX = end.x - start.x;
float dY = end.y - start.y;
float distance = sqrtf(dX * dX + dY * dY); // simple pythagorus
//unit vector is just the difference divided by the distance
CGSize unitVector = CGSizeMake(dX/distance, dY/distance);
return unitVector;
}
Note... it doesn't matter which way round the start and end are as squaring the numbers will only give positive values.
Now you can use this vector to get to any point along the line between the two points (centre of the circle and target).
So, the start of the line is ...
CGPoint center = // center of circle
CGPoint target = // target
float radius = //radius of circle
float dX = center.x - target.x;
float dY = center.y - target.y;
float distance = sqrtf(dX * dX + dY * dY);
CGSize unitVector = [self unitVectorFromPoint:center toPoint:target];
CGPoint startOfLaser = CGPointMake(center.x + unitVector.x * radius, center.y + unitVector.y * radius).
CGPoint midPointOfLaser = CGPointMake(center.x + unitVecotr.x * distance * 0.5, center.y + unitVector.y * distance * 0.5);
This just multiplies the unit vector by how far you want to go (radius) to get to the point on the line at that distance.
Hope this helps :D
If you want the mid point between the two points then you just need to change "radius" to be the distance that you want to calculate and it will give you the mid point. (and so on).

How do I move forward in the direction I am pointing in a 3d world?

I am using Cocos3D. I have a camera (which acts as the first-person viewed player in a world with only a spinning "hello, world" and a spinning earth. I have made it possible for the camera to point in any direction, and I have got it to move, but how do I get the camera/player to move forward in the direction he is pointing? (He does not go up or down, i.e. his y position does not change).
I note this is quite an old question - hopefully this answer helps someone!
You need to get the rotation angle of your camera, convert that to radians and use trigonometry to get the new X-Z coordinates. Tell your camera to move to those coordinates and presto, the player has moved forward!
CC3Camera *cam = self.activeCamera;
CC3Rotator *rotator = cam.rotator;
CC3Vector ro = rotatoooor.rotation;
CC3Vector loc = cam.globalLocation;
// If the -90 is left off, you go left/right and not forward
float roA = rotator.rotationAngle-90;
// Bug in rotationAngle? Need this as 315 angle reports as 45
int roI = ro.y;
int diff = roA+90 - roI;
if (diff == 0 && roI == 45)
roA = 315-90;
double theta = roA * M_PI/180; // Convert to radians
/*
x = d cos a
z = d sin a
*/
double sinA = sin(theta);
double cosA = cos(theta);
double newX = distance*cosA + loc.x;
double newZ = distance*sinA + loc.z;
CC3Vector newTo = cc3v(newX, loc.y, newZ); // Pass this to your camera