I have this code:
CGPoint arrowMiddle = CGPointMake((arrowOne.x + arrowTo.x)/2, (arrowOne.y + arrowTo.y)/2);
CGPoint arrowLeft = CGPointMake(arrowMiddle.x-40, arrowMiddle.y);
CGPoint arrowRight = CGPointMake(arrowMiddle.x, arrowMiddle.y + 40);
[arrowPath addLineToScreenPoint:arrowLeft];
[arrowPath addLineToScreenPoint:arrowMiddle];
[arrowPath addLineToScreenPoint:arrowRight];
[[mapContents overlay] addSublayer:arrowPath];
[arrowPath release];
with this output:
http://img517.yfrog.com/img517/7690/schermafbeelding2010032.png
What have i to add to get the left and right the at same degree of the line + 30°.
If someone has the algorithm of drawing an arrow on a line, pleas give it. It doesn't matter what programming language it is...
Thanks
Here is what you do. First, take the vector of the line and normalize it by dividing it by its length — this will give you a vector of length 1 pointing in the direction of the line. Next, multiply it by the length you need it to be. Turn it by 120° and -120° to make the arrow. Finally, offset it by the coordinates where you want it to be. Here is how it would look like in code:
// calculate the position of the arrow
CGPoint arrowMiddle;
arrowMiddle.x = (arrowOne.x + arrowTo.x) / 2;
arrowMiddle.y = (arrowOne.y + arrowTo.y) / 2;
// create a line vector
CGPoint v;
v.x = arrowTo.x - arrowOne.x;
v.y = arrowTo.y - arrowOne.y;
// normalize it and multiply by needed length
CGFloat length = sqrt(v.x * v.x + v.y * v.y);
v.x = 40 * (v.x / length);
v.y = 40 * (v.y / length);
// turn it by 120° and offset to position
CGPoint arrowLeft = CGPointApplyAffineTransform(v, CGAffineTransformMakeRotation(3.14 * 2 / 3));
arrowLeft.x = arrowLeft.x + arrowMiddle.x;
arrowLeft.y = arrowLeft.y + arrowMiddle.y;
// turn it by -120° and offset to position
CGPoint arrowRight = CGPointApplyAffineTransform(v, CGAffineTransformMakeRotation(-3.14 * 2 / 3));
arrowRight.x = arrowRight.x + arrowMiddle.x;
arrowRight.y = arrowRight.y + arrowMiddle.y;
Thanks for respond!
In the meanwhile I found also an solution.
It's Like this:
double slopy , cosy , siny;
double Par = 10.0; //length of Arrow (>)
slopy = atan2( ( arrowOne.y - arrowTo.y ),
( arrowOne.x - arrowTo.x ) );
cosy = cos( slopy );
siny = sin( slopy ); //need math.h for these functions
CGPoint arrowMiddle = CGPointMake((arrowOne.x + arrowTo.x)/2, (arrowOne.y + arrowTo.y)/2);
[arrowPath addLineToScreenPoint:arrowMiddle];
CGPoint arrowLeft = CGPointMake( arrowMiddle.x + round( - Par * cosy - ( Par / 2.0 * siny ) ), arrowMiddle.y + round( - Par * siny + ( Par / 2.0 * cosy ) ) );
[arrowPath addLineToScreenPoint:arrowLeft];
CGPoint arrowRight = CGPointMake( arrowMiddle.x + round( - Par * cosy + ( Par / 2.0 * siny ) ),arrowMiddle.y - round( Par / 2.0 * cosy + Par * siny ) );
[arrowPath addLineToScreenPoint:arrowRight];
[arrowPath addLineToScreenPoint:arrowMiddle];
[[mapContents overlay] addSublayer:arrowPath];
[arrowPath release];
The only problem here is that i draw it like it's an RMPath(route-me framework) and that the arrow gets bigger/smaller when you zoom in/out.
But thanks for respond, I will look into it which code is the most perform.
Related
I am following the quaternion tutorial: http://www.raywenderlich.com/12667/how-to-rotate-a-3d-object-using-touches-with-opengl and am trying to rotate a globe to some XYZ location. I have an initial quaternion and generate a random XYZ location on the surface of the globe. I pass that XYZ location into the following function. The idea was to generate a lookAt vector with GLKMatrix4MakeLookAt and define the end Quaternion for the slerp step from the lookAt matrix.
- (void)rotateToLocationX:(float)x andY:(float)y andZ:(float)z {
// Turn on the interpolation for smooth rotation
_slerping = YES; // Begin auto rotating to this location
_slerpCur = 0;
_slerpMax = 1.0;
_slerpStart = _quat;
// The eye location is defined by the look at location multiplied by this modifier
float modifier = 1.0;
// Create a look at vector for which we will create a GLK4Matrix from
float xEye = x;
float yEye = y;
float zEye = z;
//NSLog(#"%f %f %f %f %f %f",xEye, yEye, zEye, x, y, z);
_currentSatelliteLocation = GLKMatrix4MakeLookAt(xEye, yEye, zEye, 0, 0, 0, 0, 1, 0);
_currentSatelliteLocation = GLKMatrix4Multiply(_currentSatelliteLocation,self.effect.transform.modelviewMatrix);
// Turn our 4x4 matrix into a quat and use it to mark the end point of our interpolation
//_currentSatelliteLocation = GLKMatrix4Translate(_currentSatelliteLocation, 0.0f, 0.0f, GLOBAL_EARTH_Z_LOCATION);
_slerpEnd = GLKQuaternionMakeWithMatrix4(_currentSatelliteLocation);
// Print info on the quat
GLKVector3 vec = GLKQuaternionAxis(_slerpEnd);
float angle = GLKQuaternionAngle(_slerpEnd);
//NSLog(#"%f %f %f %f",vec.x,vec.y,vec.z,angle);
NSLog(#"Quat end:");
[self printMatrix:_currentSatelliteLocation];
//[self printMatrix:self.effect.transform.modelviewMatrix];
}
The interpolation works, I get a smooth rotation, however the ending location is never the XYZ I input - I know this because my globe is a sphere and I am calculating XYZ from Lat Lon. I want to look directly down the 'lookAt' vector toward the center of the earth from that lat/lon location on the surface of the globe after the rotation. I think it may have something to do with the up vector but I've tried everything that made sense.
What am I doing wrong - How can I define a final quaternion that when I finish rotating, looks down a vector to the XYZ on the surface of the globe? Thanks!
Is the following your meaning:
Your globe center is (0, 0, 0), radius is R, the start position is (0, 0, R), your final position is (0, R, 0), so rotate the globe 90 degrees around X-asix?
If so, just set lookat function eye position to your final position, the look at parameters to the globe center.
m_target.x = 0.0f;
m_target.y = 0.0f;
m_target.z = 1.0f;
m_right.x = 1.0f;
m_right.y = 0.0f;
m_right.z = 0.0f;
m_up.x = 0.0f;
m_up.y = 1.0f;
m_up.z = 0.0f;
void CCamera::RotateX( float amount )
{
Point3D target = m_target;
Point3D up = m_up;
amount = amount / 180 * PI;
m_target.x = (cos(PI / 2 - amount) * up.x) + (cos(amount) * target.x);
m_target.y = (cos(PI / 2 - amount) * up.y) + (cos(amount) * target.y);
m_target.z = (cos(PI / 2 - amount) * up.z) + (cos(amount) * target.z);
m_up.x = (cos(amount) * up.x) + (cos(PI / 2 + amount) * target.x);
m_up.y = (cos(amount) * up.y) + (cos(PI / 2 + amount) * target.y);
m_up.z = (cos(amount) * up.z) + (cos(PI / 2 + amount) * target.z);
Normalize(m_target);
Normalize(m_up);
}
void CCamera::RotateY( float amount )
{
Point3D target = m_target;
Point3D right = m_right;
amount = amount / 180 * PI;
m_target.x = (cos(PI / 2 + amount) * right.x) + (cos(amount) * target.x);
m_target.y = (cos(PI / 2 + amount) * right.y) + (cos(amount) * target.y);
m_target.z = (cos(PI / 2 + amount) * right.z) + (cos(amount) * target.z);
m_right.x = (cos(amount) * right.x) + (cos(PI / 2 - amount) * target.x);
m_right.y = (cos(amount) * right.y) + (cos(PI / 2 - amount) * target.y);
m_right.z = (cos(amount) * right.z) + (cos(PI / 2 - amount) * target.z);
Normalize(m_target);
Normalize(m_right);
}
void CCamera::RotateZ( float amount )
{
Point3D right = m_right;
Point3D up = m_up;
amount = amount / 180 * PI;
m_up.x = (cos(amount) * up.x) + (cos(PI / 2 - amount) * right.x);
m_up.y = (cos(amount) * up.y) + (cos(PI / 2 - amount) * right.y);
m_up.z = (cos(amount) * up.z) + (cos(PI / 2 - amount) * right.z);
m_right.x = (cos(PI / 2 + amount) * up.x) + (cos(amount) * right.x);
m_right.y = (cos(PI / 2 + amount) * up.y) + (cos(amount) * right.y);
m_right.z = (cos(PI / 2 + amount) * up.z) + (cos(amount) * right.z);
Normalize(m_right);
Normalize(m_up);
}
void CCamera::Normalize( Point3D &p )
{
float length = sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
if (1 == length || 0 == length)
{
return;
}
float scaleFactor = 1.0 / length;
p.x *= scaleFactor;
p.y *= scaleFactor;
p.z *= scaleFactor;
}
The answer to this question is a combination of the following rotateTo function and a change to the code from Ray's tutorial at ( http://www.raywenderlich.com/12667/how-to-rotate-a-3d-object-using-touches-with-opengl ). As one of the comments on that article says there is an arbitrary factor of 2.0 being multiplied in GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle * 2.0, axis);. Remove that "2" and use the following function to create the _slerpEnd - after that the globe will rotate smoothly to XYZ specified.
// Rotate the globe using Slerp interpolation to an XYZ coordinate
- (void)rotateToLocationX:(float)x andY:(float)y andZ:(float)z {
// Turn on the interpolation for smooth rotation
_slerping = YES; // Begin auto rotating to this location
_slerpCur = 0;
_slerpMax = 1.0;
_slerpStart = _quat;
// Create a look at vector for which we will create a GLK4Matrix from
float xEye = x;
float yEye = y;
float zEye = z;
_currentSatelliteLocation = GLKMatrix4MakeLookAt(xEye, yEye, zEye, 0, 0, 0, 0, 1, 0);
// Turn our 4x4 matrix into a quat and use it to mark the end point of our interpolation
_slerpEnd = GLKQuaternionMakeWithMatrix4(_currentSatelliteLocation);
}
I am facing a typical problem in rotating an object. Description is as given below
I have taken two CGPoint let say point1 and point2
point1 = (50,50)
point2 = (150, 50)
this point will draw a horizontal line.
Now i am drawing a rectangle with that point on it. Width is 100 and height is 10. Angle is 0.see screen shot
works fine
now i change the point let say
point1 = (50,50)
point2 = (50,150)
this point will draw a vertical line.
For rectangle Angle is 90. With this point rectangle is not drawing properlysee screen shot
My code for drawing rectangle is :
CGPoint mid = CGPointMake((point1.x+point2.x)/2, (point1.y+point2.y)/2)
CGPoint UL = CGPointMake(mid.x + ( Width / 2 ) * cos (A) - ( Height / 2 ) * sin (A) , mid.y + ( Height / 2 ) * cos (A) + ( Width / 2 ) * sin (A));
CGContextMoveToPoint(context, UL.x,routeView.frame.size.height - UL.y);
CGPoint UR = CGPointMake(mid.x - ( Width / 2 ) * cos (A) - ( Height / 2 ) * sin (A) , mid.y + ( Height / 2 ) * cos (A) - ( Width / 2 ) * sin (A));
CGContextAddLineToPoint(context, UR.x,routeView.frame.size.height - UR.y);
CGPoint BR = CGPointMake(mid.x - ( Width / 2 ) * cos (A) + ( Height / 2 ) * sin (A) , mid.y - ( Height / 2 ) * cos (A) - ( Width / 2 ) * sin (A));
CGContextAddLineToPoint(context, BR.x,routeView.frame.size.height - BR.y);
CGPoint BL = CGPointMake(mid.x + ( Width / 2 ) * cos (A) + ( Height / 2 ) * sin (A) , mid.y - ( Height / 2 ) * cos (A) + ( Width / 2 ) * sin (A));
CGContextAddLineToPoint(context, BL.x,routeView.frame.size.height - BL.y);
CGContextAddLineToPoint(context, UL.x,routeView.frame.size.height - UL.y);
CGContextStrokePath(context);
Here A is Angle and it is not static, mid is middle point of point1 and point2
for more ref see this
Am I missing something?
Please help me if you have any idea.......
Thanks,
Let me guess, it's actually rotated about 26 degrees too far, right?
(90 x 180) / Pi ~= 5156.62 = (360 x 14) + 90 + 26.62
You rotated it 90 radians by mistake.
I used the code as follow to implement the effect of replace scenes:
[[CCDirector sharedDirector] replaceScene:[CCTransitionPageTurn transitionWithDuration:0.5f scene:alterScene backwards:NO]];
Then, the effect that is reversed to appear as if the incoming scene is being turned from left over the outgoing scene.
I want to get the effect turn from top over. how can I get this?
Modify cocos2d/CCActionPageTurn3D.m to exchange x for y.
/*
* cocos2d for iPhone: http://www.cocos2d-iphone.org
*
* Copyright (c) 2009 Sindesso Pty Ltd http://www.sindesso.com/
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#import "CCActionPageTurn3D.h"
#implementation CCPageTurn3D
/*
* Update each tick
* Time is the percentage of the way through the duration
*/
-(void)update:(ccTime)time
{
float tt = MAX( 0, time - 0.25f );
float deltaAx = ( tt * tt * 500);
float ax = -100 - deltaAx;
float deltaTheta = - (float) M_PI_2 * sqrtf( time);
float theta = /*0.01f*/ + (float) M_PI_2 +deltaTheta;
float sinTheta = sinf(theta);
float cosTheta = cosf(theta);
for( int i = 0; i <=gridSize_.x; i++ )
{
for( int j = 0; j <= gridSize_.y; j++ )
{
// Get original vertex
ccVertex3F p = [self originalVertex:ccg(i,j)];
float R = sqrtf(p.y*p.y + (p.x - ax) * (p.x - ax));
float r = R * sinTheta;
float alpha = asinf( p.y / R );
float beta = alpha / sinTheta;
float cosBeta = cosf( beta );
// If beta > PI then we've wrapped around the cone
// Reduce the radius to stop these points interfering with others
if( beta <= M_PI)
p.y = ( r * sinf(beta));
else
{
// Force X = 0 to stop wrapped
// points
p.y = 0;
}
p.x = ( R + ax - ( r*(1 - cosBeta)*sinTheta));
// We scale z here to avoid the animation being
// too much bigger than the screen due to perspectve transform
p.z = (r * ( 1 - cosBeta ) * cosTheta) / 7; // "100" didn't work for
// Stop z coord from dropping beneath underlying page in a transition
// issue #751
if( p.z<0.5f )
p.z = 0.5f;
// Set new coords
[self setVertex:ccg(i,j) vertex:p];
}
}
}
#end
diff --git a/cocos2d/CCActionPageTurn3D.m b/cocos2d/CCActionPageTurn3D.m
index ee59500..ccd64eb 100644
--- a/cocos2d/CCActionPageTurn3D.m
+++ b/cocos2d/CCActionPageTurn3D.m
## -34,10 +34,10 ##
-(void)update:(ccTime)time
{
float tt = MAX( 0, time - 0.25f );
- float deltaAy = ( tt * tt * 500);
- float ay = -100 - deltaAy;
+ float deltaAx = ( tt * tt * 500);
+ float ax = -100 - deltaAx;
float deltaTheta = - (float) M_PI_2 * sqrtf( time) ;
float theta = /*0.01f*/ + (float) M_PI_2 +deltaTheta;
float sinTheta = sinf(theta);
## -50,24 +50,24 ##
// Get original vertex
ccVertex3F p = [self originalVertex:ccg(i,j)];
- float R = sqrtf(p.x*p.x + (p.y - ay) * (p.y - ay));
+ float R = sqrtf(p.y*p.y + (p.x - ax) * (p.x - ax));
float r = R * sinTheta;
- float alpha = asinf( p.x / R );
+ float alpha = asinf( p.y / R );
float beta = alpha / sinTheta;
float cosBeta = cosf( beta );
// If beta > PI then we've wrapped around the cone
// Reduce the radius to stop these points interfering with others
if( beta <= M_PI)
- p.x = ( r * sinf(beta));
+ p.y = ( r * sinf(beta));
else
{
// Force X = 0 to stop wrapped
// points
- p.x = 0;
+ p.y = 0;
}
- p.y = ( R + ay - ( r*(1 - cosBeta)*sinTheta));
+ p.x = ( R + ax - ( r*(1 - cosBeta)*sinTheta));
// We scale z here to avoid the animation being
// too much bigger than the screen due to perspectve transform
Im trying to understanding collision detection in 2d world. I recently got this tutorials http://www.gotoandplay.it/_articles/2003/12/bezierCollision.php. I have question which puzzled me a lot - on the flash demo ball is dropping without responding if i try to swap the starting and end point.
Can someone explain me , how the simulation works.
I have modified this the sample code. It works perfect until the start and end point are swapped, Here is same code in objective c
Thanks in advance. .
-(void)render:(ccTime)dt {
if(renderer)
{
CGPoint b = ball.position;
float bvx = ball.vx;
float bvy = ball.vy;
bvx += .02;
bvy -= .2;
b.x += bvx;
b.y += bvy;
float br = ball.contentSize.width/2;
for ( int p = 0 ; p < [map count] ; p++ ) {
line *l = [map objectAtIndex:p];
CGPoint p0 = l.end;
CGPoint p1 = l.start;
float p0x = p0.x, p0y = p0.y, p1x = p1.x, p1y = p1.y;
// get Angle //
float dx = p0x - p1x;
float dy = p0y - p1y;
float angle = atan2( dy , dx );
float _sin = sin ( angle );
float _cos = cos ( angle );
// rotate p1 ( need only 'x' ) //
float p1rx = dy * _sin + dx * _cos + p0x;
// rotate ball //
float px = p0x - b.x;
float py = p0y - b.y;
float brx = py * _sin + px * _cos + p0x;
float bry = py * _cos - px * _sin + p0y;
float cp = ( b.x - p0x ) * ( p1y - p0y ) - ( b.y - p0y ) * ( p1x - p0x );
if ( bry > p0y - br && brx > p0x && brx < p1rx && cp > 0 ) {
// calc new Vector //
float vx = bvy * _sin + bvx * _cos;
float vy = bvy * _cos - bvx * _sin;
vy *= -.8;
vx *= .98;
float __sin = sin ( -angle );
float __cos = cos ( -angle );
bvx = vy * __sin + vx * __cos;
bvy = vy * __cos - vx * __sin;
// calc new Position //
bry = p0y - br;
dx = p0x - brx;
dy = p0y - bry;
b.x = dy * __sin + dx * __cos + p0x;
b.y = dy * __cos - dx * __sin + p0y;
}
}
ball.position = b;
ball.vx = bvx;
ball.vy = bvy;
if ( b.y < 42)
{
ball.position = ccp(50, size.height - 42);
ball.vx = .0f;
ball.vy = .0f;
}
}
}
The order of the points defines an orientation on the curve. If the start point is on the left and the end point on the right, then the curve is oriented so that "up" points above the curve. However, if you swap the start/end points the curve is oppositely oriented, so now "up" actually points below the curve.
When your code detects a collision and then corrects the velocity it is using the curve's orientation. That is why when the ball drops on the curve with the start/end points swapped it appears to jump through the curve.
To correct this your collision resolution code should check which side of the curve the ball is on (with respect to the curve's orientation), and adjust accordingly.
If you swap l.end and l.start it will serve for line without the segment (l.start, l.end). This is because all values are signed here.
Algorithm turns the plane so that line is horizontal and one of the segment ends doesn't move. After that it is easy to understand whether the ball touches the line. And if it does, its speed should change: in rotated plane it just reverses y-coordinate and we should rotate it back to get line not horizontal again.
In fact not a very good implementation. All this can be done without sin, cos, just vectors.
I am using the framework of route-me for working with locations.
In this code the path between two markers(points) will be drawn as a line.
My Question: "What code should I add if I want to add an arrow in the middle(or top) of the line, so that it points the direction"
Thanks
- (void)drawInContext:(CGContextRef)theContext
{
renderedScale = [contents metersPerPixel];
float scale = 1.0f / [contents metersPerPixel];
float scaledLineWidth = lineWidth;
if(!scaleLineWidth) {
scaledLineWidth *= renderedScale;
}
//NSLog(#"line width = %f, content scale = %f", scaledLineWidth, renderedScale);
CGContextScaleCTM(theContext, scale, scale);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, path);
CGContextSetLineWidth(theContext, scaledLineWidth);
CGContextSetStrokeColorWithColor(theContext, [lineColor CGColor]);
CGContextSetFillColorWithColor(theContext, [fillColor CGColor]);
// according to Apple's documentation, DrawPath closes the path if it's a filled style, so a call to ClosePath isn't necessary
CGContextDrawPath(theContext, drawingMode);
}
- (void) drawLine: (CGContextRef) context from: (CGPoint) from to: (CGPoint) to
{
double slopy, cosy, siny;
// Arrow size
double length = 10.0;
double width = 5.0;
slopy = atan2((from.y - to.y), (from.x - to.x));
cosy = cos(slopy);
siny = sin(slopy);
//draw a line between the 2 endpoint
CGContextMoveToPoint(context, from.x - length * cosy, from.y - length * siny );
CGContextAddLineToPoint(context, to.x + length * cosy, to.y + length * siny);
//paints a line along the current path
CGContextStrokePath(context);
//here is the tough part - actually drawing the arrows
//a total of 6 lines drawn to make the arrow shape
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context,
from.x + ( - length * cosy - ( width / 2.0 * siny )),
from.y + ( - length * siny + ( width / 2.0 * cosy )));
CGContextAddLineToPoint(context,
from.x + (- length * cosy + ( width / 2.0 * siny )),
from.y - (width / 2.0 * cosy + length * siny ) );
CGContextClosePath(context);
CGContextStrokePath(context);
/*/-------------similarly the the other end-------------/*/
CGContextMoveToPoint(context, to.x, to.y);
CGContextAddLineToPoint(context,
to.x + (length * cosy - ( width / 2.0 * siny )),
to.y + (length * siny + ( width / 2.0 * cosy )) );
CGContextAddLineToPoint(context,
to.x + (length * cosy + width / 2.0 * siny),
to.y - (width / 2.0 * cosy - length * siny) );
CGContextClosePath(context);
CGContextStrokePath(context);
}
The drawing of the actual triangle/arrow is easy once you have two points on your path.
CGContextMoveToPoint( context , ax , ay );
CGContextAddLineToPoint( context , bx , by );
CGContextAddLineToPoint( context , cx , cy );
CGContextClosePath( context ); // for triangle
Getting the points is a little more tricky. You said path was a line, as opposed to a curve or series of curves. That makes it easier.
Use CGPathApply to pick two points on the path. Probably, this is the last two points, one of which may be kCGPathElementMoveToPoint and the other will be kCGPathElementAddLineToPoint. Let mx,my be the first point and nx,ny be the second, so the arrow will point from m towards n.
Assuming you want the arrow at the tip of the line, bx,by from above will equal nx,ny on the line. Choose a point dx,dy between mx,my and nx,ny to calculate the other points.
Now calculate ax,ay and cx,cy such that they are on a line with dx,dy and equidistant from path. The following should be close, although I probably got some signs wrong:
r = atan2( ny - my , nx - mx );
bx = nx;
by = ny;
dx = bx + sin( r ) * length;
dy = by + cos( r ) * length;
r += M_PI_2; // perpendicular to path
ax = dx + sin( r ) * width;
ay = dy + cos( r ) * width;
cx = dx - sin( r ) * width;
cy = dy - cos( r ) * width;
Length is the distance from the tip of the arrow to the base, and width is distance from the shaft to the barbs, or half the breadth of the arrow head.
If path is a curve, then instead of finding mx,my as the previous point or move, it will be the final control point of the final curve. Each control point is on a line tangent to the curve and passing through the adjacent point.
I found this question as I had the same. I took drawnonward's example and it was so close... But with a flipping of cos and sin, I was able to get it to work:
r = atan2( ny - my , nx - mx );
r += M_PI;
bx = nx;
by = ny;
dx = bx + cos( r ) * length;
dy = by + sin( r ) * length;
r += M_PI_2; // perpendicular to path
ax = dx + cos( r ) * width;
ay = dy + sin( r ) * width;
cx = dx - cos( r ) * width;
cy = dy - sin( r ) * width;
Once I did that, my arrows were pointed exactly the wrong way. So I added that second line (r += M_PI;)
Thanks go to drawnonward!
And here is Swift 4+ version for Friedhelm Brügge answer: (I'll draw it on image)
func drawArrow(image: UIImage, ptSrc: CGPoint, ptDest: CGPoint) {
// create context with image size
UIGraphicsBeginImageContext(image.size)
let context = UIGraphicsGetCurrentContext()
// draw current image to the context
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
var slopY: CGFloat, cosY: CGFloat, sinY: CGFloat;
// Arrow size
let length: CGFloat = 35.0;
let width: CGFloat = 35.0;
slopY = atan2((ptSrc.y - ptDest.y), (ptSrc.x - ptDest.x));
cosY = cos(slopY);
sinY = sin(slopY);
//here is the tough part - actually drawing the arrows
//a total of 6 lines drawn to make the arrow shape
context?.setFillColor(UIColor.white.cgColor)
context?.move(to: CGPoint(x: ptSrc.x, y: ptSrc.y))
context?.addLine(to: CGPoint(x: ptSrc.x + ( -length * cosY - ( width / 2.0 * sinY )), y: ptSrc.y + ( -length * sinY + ( width / 2.0 * cosY ))))
context?.addLine(to: CGPoint(x: ptSrc.x + (-length * cosY + ( width / 2.0 * sinY )), y: ptSrc.y - (width / 2.0 * cosY + length * sinY )))
context?.closePath()
context?.fillPath()
context?.move(to: CGPoint(x: ptSrc.x, y: ptSrc.y))
context?.addLine(to: CGPoint(x: ptDest.x + (length * cosY - ( width / 2.0 * sinY )), y: ptDest.y + (length * sinY + ( width / 2.0 * cosY ))))
context?.addLine(to: CGPoint(x: ptDest.x + (length * cosY + width / 2.0 * sinY), y: ptDest.y - (width / 2.0 * cosY - length * sinY)))
context?.closePath()
context?.fillPath()
// draw current context to image view
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
//close context
UIGraphicsEndImageContext()
}