IOS Core Animation: Skills (4) Plane Vector-Elegant Graphic Commander

Keywords: less angular Programming Attribute

Planar vectors are quantities of both direction and magnitude in two-dimensional planes. They are also called vectors in physics, as opposed to quantities of only size and no direction.

You can ignore all the code that appears in this chapter, because they are actually there. Here.

What is the relationship between vectors and our drawing?

Data structure of vectors

The first thing we need to figure out is how to represent a vector.

I'm not going to explain vectors in detail like math class. I'm just going to explain the key points in vectors that we can apply to our drawing. If you don't know anything about vectors, it's recommended to read this article after knowing about plane vectors.

In mathematics, vectors can be expressed in many ways. If we want to use the data of vectors for drawing, we usually use the coordinate representation of vectors, that is, a coordinate point to represent a vector. For example, if we can declare a vector a= (2,3), then in fact the vector a_means that the starting point is at the origin and the ending point is at the vector in the coordinate system (2,3).

Note that since vectors are determined by direction and size, once the directions and sizes of the two vectors are equal, the two vectors are equal vectors. For example, vectors with starting point at (1,1) and ending point at (3,4) are vectors with the same direction and the same size as vectors above us. They are two vectors of the same size.

Since we can't guarantee that the starting point of the vector we need is (0,0) in the UIKit coordinate system, we must have at least two members in designing the data structure of the vector: the starting point and the ending point.

Once we have determined the starting point and the end point of a vector object, then the vector must be determined.

@interface DHVector2D : NSObject

@property (nonatomic) CGPoint startPoint;
@property (nonatomic) CGPoint endPoint;

@end

For ease of use, we can declare some initializer s to create vectors flexibly.

/**
 *  Initialize a vector with two points
 *
 *  @param start starting point
 *  @param end   Endpoint
 *
 *  @return Generated vectors
 */
- (instancetype)initWithStartPoint:(CGPoint)start endPoint:(CGPoint)end;

/**
 *  Specify an angle to generate a unit vector
 *
 *  @param radian The angle of the vector in the counterclockwise direction to the positive direction of the X-axis
 *
 *  @return Unit vector
 */
- (instancetype)initAsIdentityVectorWithAngleToXPositiveAxis:(CGFloat)radian;

/**
 *  Initialize a vector with a CGPoint as a coordinate expression starting at (0,0)
 *
 *  @param position Vector coordinate expression
 *
 *  @return Generated vectors
 */
- (instancetype)initWithCoordinateExpression:(CGPoint)position;

/**
 *  It's equivalent to copying a vector.
 *
 *  @param vector Vectors to be copied
 *
 *  @return Generated vectors
 */
+ (instancetype)vectorWithVector:(DHVector2D *)vector;

Then the realization of these methods is as follows:

- (instancetype)initWithStartPoint:(CGPoint)start endPoint:(CGPoint)end
{
    self = [super init];
    _startPoint = start;
    _endPoint = end;
    return self;
}

- (instancetype)initWithCoordinateExpression:(CGPoint)position
{
    self = [self initWithStartPoint:CGPointZero endPoint:position];

    return self;
}

+ (instancetype)vectorWithVector:(DHVector2D *)vector
{
    DHVector2D * aVector = [[DHVector2D alloc] initWithStartPoint:vector.startPoint endPoint:vector.endPoint];

    return aVector;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"start : %@, end: %@, coordinate: %@",NSStringFromCGPoint(self.startPoint),NSStringFromCGPoint(self.endPoint),NSStringFromCGPoint([self coordinateExpression])];
}

The vector initialized by initAsIdentityVectorWithAngleToXPositiveAxis method involves rotation operations, which we will implement later.

In addition to making some initialization vectors, we can directly generate some special vectors:

@interface DHVector2D (SpecialVectors)

/**
 *  x Unit Vector in Positive Axis Direction
 *
 *  @return x Unit Vector in Positive Axis Direction
 */
+ (DHVector2D *)xPositiveIdentityVector;

/**
 *  x Unit Vector in Negative Axis Direction
 *
 *  @return x Unit Vector in Negative Axis Direction
 */
+ (DHVector2D *)xNegativeIdentityVector;

/**
 *  y Unit Vector in Positive Axis Direction
 *
 *  @return y Unit Vector in Positive Axis Direction
 */
+ (DHVector2D *)yPositiveIdentityVector;

/**
 *  y Unit Vector in Negative Axis Direction
 *
 *  @return y Unit Vector in Negative Axis Direction
 */
+ (DHVector2D *)yNegativeIdentityVector;

/**
 *  Zero vector
 *
 *  @return Zero vector
 */
+ (DHVector2D *)zeroVector;

@end

The realization is as follows:

@implementation DHVector2D (SpecialVectors)

#define IDENTITY_LENGTH 1

#pragma mark - special vector

+ (DHVector2D *)xPositiveIdentityVector
{
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointMake(IDENTITY_LENGTH, 0)];
    return vector;
}

+ (DHVector2D *)xNegativeIdentityVector
{
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointMake(-IDENTITY_LENGTH, 0)];
    return vector;
}

+ (DHVector2D *)yPositiveIdentityVector
{
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointMake(0, IDENTITY_LENGTH)];
    return vector;
}

+ (DHVector2D *)yNegativeIdentityVector
{
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointMake(0, -IDENTITY_LENGTH)];
    return vector;
}

+ (DHVector2D *)zeroVector
{
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointZero];

    return vector;
}

@end

Attributes of vectors

Vectors have many directly computed attributes, such as the length of the vector and the angle between the vector and another vector. We define these attributes as instance methods for external direct invocation.

The coordinate expression of vectors

That is, the standard expression of a vector, which we mentioned in the previous section, uses a coordinate point to represent a vector. The expression takes the starting point as the origin and the end point as the value to represent a vector. So how should the coordinate expression of any vector be realized? In fact, it is very simple to find a vector a_whose starting point is at the origin, so that it is equal to the original vector (the direction and size are the same as the original vector), then the end point of the vector a_is the coordinate expression of the original vector. This description is used to facilitate the understanding of coordinate expressions, and we can usually apply formulas directly when calculating:

For a vector a_, the starting point is (x1,y1) and the ending point is (x2,y2), then its coordinate expression is:

a⃗ =(x2−x1,y2−y1)

Vector equality

If the coordinate expressions of two vectors are the same, then they are equal vectors.

Vector Length

Vector length is also called vector modulus. Sometimes we need to calculate the length of a vector directly. What we know is only the coordinates of the starting and ending points of the vector. In fact, this formula is widely used in analytic geometry of high school mathematics. The calculation of vector length is a calculation of Pythagorean theorem.

For a vector a_, the starting point is (x1,y1) and the ending point is (x2,y2). If the length of the vector is regarded as an oblique edge of a right triangle, then the lengths of the two right edges are | x2_x1 | and | Y2 Y1 | (the absolute value symbol should be known), and then the oblique length is calculated by using the Pythagorean theorem, which is the modulus of the vector:

|a⃗ |=(x2−x1)2+(y2−y1)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√

Because we take the square, we can guarantee that the value is positive and the absolute value can be removed.

If the vector is expressed by coordinate expression, that is:

a⃗ =(x,y)

If its starting point is (0,0), then the modulus of the vector is:

|a⃗ |=(x−0)2+(y−0)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√=x2+y2‾‾‾‾‾‾‾√

This is obviously a Pythagorean theorem.

angle between two vectors

The angle between vectors represents the angle whose radian is less than PI when the starting point of two vectors is the same. Usually we use their coordinate expression to calculate the angle (because when using the coordinate expression, the starting point of the two vectors is the origin, which satisfies the condition of calculating the angle).

Here we directly apply the formula of vectors. In fact, the formula for calculating the angle between vectors comes from the geometric meaning of the point product of vectors. We will explain the point product below.

If there are two vectors a_; = (x1,y1) and b= (x2,y2), then their angle a satisfies:

cosα=a⃗ ⋅b⃗ |a⃗ ||b⃗ |=x1y1+x2y2x21+y21‾‾‾‾‾‾‾√x22+y22‾‾‾‾‾‾‾√

Angle of arrival of vectors

Understand the angle between vectors, and then understand the angle of arrival of vectors. The arrival angle of a vector refers to the angle at which one of the vectors rotates in one direction (clockwise or counterclockwise) and is collinear with the other vectors when the starting point of the two vectors is the same. So there are two cases of angle arrival: clockwise to angle and counterclockwise to angle. It's easy to get that clockwise to angle = 2 pi - counterclockwise to angle, and the angle smaller than Pi is the angle.

So how to calculate the arrival angle? There is no direct formula for calculating the arrival angle. We can only calculate the arrival angle by analytic reasoning.
If there are two vectors a= (x1,y1) and b= (x2,y2), then a_to b_are calculated in the counterclockwise to angular way:
If b_is in the anticlockwise direction of a_ (the angle is less than pi), then angle = angle; if not, angle = 2 pi - angle.

In programming, we can use this method to calculate:

If there are two vector objects a and b, if we want to calculate the clockwise angle of a to b, then we first determine whether B is in the clockwise direction of A. If B is in, then the angle that should be obtained is the angle between a and B. If not, the angle that should be obtained is the angle between 2 pi-A and B.

So how to determine whether B is clockwise in a? We can first get the angle between a and b, and save it with a temporary variable angle1. Then we rotate a clockwise for one degree, and then get the angle between a and b, and save it with angle2. If angle2 < angle1, then the angle between a and B becomes smaller after clockwise rotation, then B is in the clockwise direction of a, otherwise B is in the clockwise direction of A. Not in the clockwise direction of A.

Exciting Realization Links

category Statement

@interface DHVector2D (VectorDescriptions)

/**
 *  Vector length
 *
 *  @return Vector length
 */
- (CGFloat)length;

/**
 *  The starting point and direction remain unchanged, and the endpoint position is changed so that the length of the vector equals the length of the incoming vector.
 *
 *  @param length Length to change
 */
- (void)setLength:(CGFloat)length;

/**
 *  The angle between vectors is the radian value,<=PI
 *
 *  @param oVector The angle to this vector
 *
 *  @return Angle radian value
 */
- (CGFloat)angleOfOtherVector:(DHVector2D *)oVector;

/**
 *  Angle in the positive direction of the x-axis
 *
 *  @return Angle radian value
 */
- (CGFloat)angleOfXAxisPositiveVector;

/**
 *  The coordinate form of a vector, that is, the position of the termination point after the starting point is moved to the origin.
 *
 *  @return Vector coordinate expression
 */
- (CGPoint)coordinateExpression;

/**
 *  Judging whether two vectors are equal
 *
 *  @param aVector Vectors to be compared
 *
 *  @return Is it equal?
 */
- (BOOL)isEqualToVector:(DHVector2D *)aVector;

/**
 *  Clockwise to angular from the vector to a vector
 *
 *  @param vector Vectors to be arrived at
 *
 *  @return Arrival radian value
 */
- (CGFloat)clockwiseAngleToVector:(DHVector2D *)vector;

/**
 *  The counterclockwise to the angle from the vector to a vector
 *
 *  @param vector Vectors to be arrived at
 *
 *  @return Arrival radian value
 */
- (CGFloat)antiClockwiseAngleToVector:(DHVector2D *)vector;


@end

category implementation

@implementation DHVector2D (VectorDescriptions)

- (CGFloat)length
{
    return sqrt(pow((_startPoint.y - _endPoint.y), 2) + pow((_startPoint.x - _endPoint.x), 2));
}

- (void)setLength:(CGFloat)length
{
    [self multipliedByNumber:length/self.length];
}

- (CGFloat)angleOfOtherVector:(DHVector2D *)oVector
{
    // Inverse Solution of Vector Angle by Geometric Meaning of Vector Point Product
    CGFloat cos = [self dotProductedByOtherVector:oVector] / ([self length] * [oVector length]);
    return acos(cos);
}

- (CGFloat)angleOfXAxisPositiveVector
{
    return [self angleOfOtherVector:[DHVector2D xPositiveIdentityVector]];
}

- (CGPoint)coordinateExpression
{
    CGPoint p = CGPointZero;

    p.x = _endPoint.x - _startPoint.x;
    p.y = _endPoint.y - _startPoint.y;

    return p;
}

- (BOOL)isEqualToVector:(DHVector2D *)aVector
{
    CGPoint selfExpression = [self coordinateExpression];
    CGPoint expression = [aVector coordinateExpression];
    return CGPointEqualToPoint(selfExpression, expression);
}


#pragma mark - to angle

// Clockwise to angle = counterclockwise to angle from another vector to that vector
- (CGFloat)clockwiseAngleToVector:(DHVector2D *)vector
{
    return [vector antiClockwiseAngleToVector:self];
}

// If another vector is in the counterclockwise direction of the vector (the angle is less than PI), then the angle equals the angle.
// If not, the angle = 2PI - included angle
- (CGFloat)antiClockwiseAngleToVector:(DHVector2D *)vector
{
    // Determine whether it is counterclockwise
    // First, if their angle is PI, they return directly.
    if ([self angleOfOtherVector:vector] == M_PI) {

        return M_PI;
    }


    // Then, the vectors are rotated one degree counter-clockwise, and if their angle decreases, they are in the counter-clockwise direction.

    CGFloat angle = [self angleOfOtherVector:vector];

    DHVector2D * tempVector = [DHVector2D vectorWithVector:self];
    [tempVector rotateAntiClockwiselyWithRadian:0.01/180.f*M_PI];
    if ([tempVector angleOfOtherVector:vector] < angle) {
        return angle;
    }

    return (2 * M_PI) - angle;
}

@end

setter of Vector Length

One thing to note here is the setLength method, which is obviously a computational property for vectors. We mentioned the attribute relationship between layer and view in the first article of this topic, and proved that frame is calculated by center and bounds together. That is to say, frame is related to center and bounds in both directions. Once center or bounds change, frame will change, and frame will send out. If change occurs, then center or bounds will also change, which is the characteristic of computational attributes.

So obviously, our length depends on the value of the starting point and the ending point of the vector, so our getter of length is calculated by the starting point and the ending point. Similarly, once the value of length changes, the value of the starting point and the ending point should also change, so the setter of length should be the root. The new starting and ending points are calculated according to the new length and the original starting and ending points. Generally speaking, if we want to change the length of a vector actively, we will keep the starting point unchanged and move the termination point.

setLength only changes the length without changing the direction of the vector, so we should determine the direction based on the values of the previous starting and ending points, and then change the length of the vector along this direction.

So you can think about how to keep the direction unchanged.

Let's consider the simplest case, that is, the coordinate expression of a vector: a_; = (x,y), which is actually similar to a first-order curve (linear curve, or image of a function): y=kx. So the direction of the curve is actually determined by the slope, that is, the slope k=yx. When we change the coordinate expression of the vector, we can change the length of the vector and the direction of the vector as long as the slope remains unchanged.

So how do we keep the value of k unchanged while changing the values of x and y? Obviously, x and y should scale the same multiples at the same time, so their ratios will remain unchanged.

So we get a solution: for a vector a= (x,y), regardless of its previous length, we first get its slope k=yx. If we want to set the new length to l, the new coordinate expression of the vector is a= (x', y'), then we will get two equations:

Slope unchanged: k=y'x'= yx

The length is l:l=x'2+y'2_

The l, x and Y in the equation system are known quantities, so that we can get the values of x'and y'. Here we will get two solutions, one of which is the solution in opposite direction. You can see that the situation is excluded.

This is a solution. Of course, we have a simpler solution: according to the similar triangle theorem.

Let's set the end point of vector a= (x,y) as A, then the coordinates of point A are (x,y). The geometric meaning of point X and Y is the projection size of point A on the X and Y axes. We draw one of the projections, that is, from point A to the X axis, vertical foot is D1, if the origin is O. So it's obvious that Delta AD1O is a right triangle. The lengths of the two right edges are OD1=x and AD1=y, respectively. The length of the oblique edge is the modulus of the vector.

Next, we set the length of the vector a= (x,y) to l, that is, the moving point A, so that the length of the vector a= (x,y) is the same as the direction of the quantity. Let's set the moving termination point as A'(x', y') and make the vertical line in the same way, and the vertical foot is D2. Then we get a new right triangle (AD2O). The lengths of the two right sides are OD2=x', and AD2=y', respectively. Obviously, we are going to solve the values of X'and y', which can also be converted into geometric problems, solving OD2 and AD2. Length.

Because OAA's three points are collinear, then AD2O and AD1O are similar triangles. From the ratio of edges to lengths of similar triangles, we can conclude that AD2O and AD1O are similar triangles.

|a⃗ |l=xx′=yy′

Where | a | is the original vector length, l is the length we want to set, all known, X and y are also known, so we bypass the pit of slope, and calculate the coordinate expression of the new vector (x', y') through simple ratio relationship.

My implementation in the code is using the quantity product of vectors. The idea is similar to the similar triangle here. We will explain the quantity product in the vector algorithm next.

Vector Computing

In plane vectors, the calculation between vectors can be carried out, such as vector addition, subtraction, quantity product, inner product and so on. Through these algorithms, we can simplify some practical problems and solve them in a very simple form.

Vector addition

In physics, the addition of vectors, that is, the addition of vectors, can be expressed as the resultant force of two forces, using the parallelogram rule. In mathematics, we can use the coordinate expression of vectors to solve the vector addition very conveniently.

If there is

a⃗ =(x1,y1),b⃗ =(x2,y2),c⃗ =a⃗ +b⃗ 

that

c⃗ =(x1+x2,y1+y2)

In fact, the addition of vectors can be simply understood as: after all the vectors involved in the addition are connected at the end, the initial starting point and the final end point are the starting point and the end point of the sum vector.

Vector addition supports commutative and associative laws (don't ask me what commutative and associative laws are).

Vector subtraction

Vector subtraction is the inverse operation of addition, so we just need to calculate the addition in reverse.

If there is

a⃗ =(x1,y1),b⃗ =(x2,y2),c⃗ =a⃗ −b⃗ 

that

c⃗ =(x1−x2,y1−y2)

This proof is obvious.

Vector Quantity Product

Although a vector cannot add or subtract a natural number, it can be multiplied by a natural number. This is very understandable:

If there is a= (x,y), then a_2 means a; +a= (2x,2y), so the product of the number of vectors is to multiply X and y of the coordinate expression of vectors by this natural number, that is, for any vector a= (x,y) and any natural number d:

a⃗ ⋅d=(d⋅x,d⋅y)

In fact, multiplying a vector by a natural number n is an extension of the vector n times.

Vector inner product

I don't know if you remember the formula of the work done by force F_in high school physics.

W=F⃗ ⋅s⃗ 

It can be seen that both force F and displacement s are vectors, and the work done is obviously a scalar.
Let's write the formula in its entirety:

W=F⃗ ⋅s⃗ =|F⃗ |⋅|s⃗ |cosθ

Theta is the angle formed by force and displacement. If the force and displacement are in the same direction, then theta = 0, costheta = 1. If the direction of force and displacement is opposite, then theta = pi, costheta = _1, that is, the force will do negative work.

F_cos theta is actually the component of force in the direction of displacement. This is the geometric meaning of the inner product of vectors.

The formulas for the inner product of quantities are as follows:

If there is

a⃗ =(x1,y1),b⃗ =(x2,y2)

that

a⃗ ⋅b⃗ =x1⋅x2+y1⋅y2

This is the matrix multiplication formula in linear algebra (one-dimensional matrix is vector). Don't ask me why, it's good to learn linear algebra.

We used to solve the angle between vectors by the formula of vectors and the geometric meaning of vectors.

Exciting Realization Links

category's statement

@interface DHVector2D (VectorArithmetic)

/**
 *  Vector addition, which itself is added by another vector, will change its endpoint.
 *
 *  @param vector Vectors to be added
 */
- (void)plusByOtherVector:(DHVector2D *)vector;
/**
 *  Vector addition, which adds two vectors and returns them, does not affect the end point of itself.
 *
 *  @param aVector Vectors to be added
 *  @param oVector Another vector to add
 *
 *  @return The combined results
 */
+ (DHVector2D *)aVector:(DHVector2D *)aVector plusByOtherVector:(DHVector2D *)oVector;

/**
 *  This vector is subtracted by another vector: self - vector, which will affect its end point.
 *
 *  @param vector Vectors to be subtracted by themselves
 */
- (void)substractedByOtherVector:(DHVector2D *)vector;
/**
 *  Subtracting one vector from another does not affect the end point.
 *
 *  @param aVector Subtracted vector
 *  @param oVector Use this vector to subtract the subtracted vector
 *
 *  @return Subtracted vector
 */
+ (DHVector2D *)aVector:(DHVector2D *)aVector substractedByOtherVector:(DHVector2D *)oVector;

/**
 *  Multiplier, will affect its own endpoint
 *
 *  @param number Number to multiply
 */
- (void)multipliedByNumber:(CGFloat)number;
/**
 *  Multiply a vector by a number and return the multiplied vector without affecting its end point.
 *
 *  @param aVector Multiplied Vector
 *  @param number  multiplier
 *
 *  @return Multiplied vector
 */
+ (DHVector2D *)aVector:(DHVector2D *)aVector multipliedByNumber:(CGFloat)number;

/**
 *  The product of the number of vectors, the product of points, the product of one's own points multiplied by another vector, does not affect the end point of one's own.
 *
 *  @param vector Vectors of Point Multiplication
 *
 *  @return The result of dot multiplication is a scalar value.
 */
- (CGFloat)dotProductedByOtherVector:(DHVector2D *)vector;
/**
 *  Multiply one vector point by another vector, without affecting its end point.
 *
 *  @param aVector A vector
 *  @param oVector Point multiplied by another vector
 *
 *  @return The result of dot multiplication is a scalar value.
 */
+ (CGFloat)aVector:(DHVector2D *)aVector dotProductedByOtherVector:(DHVector2D *)oVector;

// Vector product (cross product, outer product)
// The direction of the cross product results will produce the third dimension, which is not considered in this category for the time being.
//- (void)crossProductedByOtherVector:(DHVector *)vector;
//+ (DHVector *)aVector:(DHVector *)aVector crossProductedByOtherVector:(DHVector *)oVector;

@end

category implementation

#pragma mark-vector operation
@implementation DHVector2D (VectorArithmetic)
// The starting point of vector returned by class method is coordinate origin

// addition
- (void)plusByOtherVector:(DHVector2D *)vector
{
    CGPoint tp = _startPoint;
    [self translationToPoint:CGPointMake(0, 0)];
    CGPoint p = [vector coordinateExpression];
    _endPoint = CGPointMake(_endPoint.x+p.x , _endPoint.y+p.y);
    [self translationToPoint:tp];
}

+ (DHVector2D *)aVector:(DHVector2D *)aVector plusByOtherVector:(DHVector2D *)oVector
{
    CGPoint p1 = [aVector coordinateExpression];
    CGPoint p2 = [oVector coordinateExpression];

    DHVector2D * vector = [[DHVector2D alloc] initWithStartPoint:CGPointMake(0, 0) endPoint:CGPointMake(p1.x+p2.x, p1.y+p2.y)];

    return vector;
}

// subtraction
// This vector is subtracted by another vector: self - vector
- (void)substractedByOtherVector:(DHVector2D *)vector
{
    CGPoint tp = _startPoint;
    [self translationToPoint:CGPointMake(0, 0)];
    CGPoint p = [vector coordinateExpression];
    _endPoint = CGPointMake(_endPoint.x - p.x, _endPoint.y - p.y);
    [self translationToPoint:tp];
}


+ (DHVector2D *)aVector:(DHVector2D *)aVector substractedByOtherVector:(DHVector2D *)oVector
{
    CGPoint p1 = [aVector coordinateExpression];
    CGPoint p2 = [oVector coordinateExpression];

    DHVector2D * vector = [[DHVector2D alloc] initWithStartPoint:CGPointMake(0, 0) endPoint:CGPointMake(p1.x-p2.x, p1.y-p2.y)];

    return vector;
}

// Number multiplication
- (void)multipliedByNumber:(CGFloat)number
{
    CGPoint startPoint = self.startPoint;
    [self translationToPoint:CGPointZero];

    self.endPoint = CGPointMake(_endPoint.x * number, _endPoint.y * number);
    [self translationToPoint:startPoint];
}

+ (DHVector2D *)aVector:(DHVector2D *)aVector multipliedByNumber:(CGFloat)number
{
    CGPoint tp = aVector.startPoint;
    [aVector translationToPoint:CGPointMake(0, 0)];
    CGPoint p = CGPointMake(aVector.endPoint.x * number, aVector.endPoint.y * number);

    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:p];

    [aVector translationToPoint:tp];

    return vector;
}

// Quantity product (dot product)
- (CGFloat)dotProductedByOtherVector:(DHVector2D *)vector
{
    return [DHVector2D aVector:self dotProductedByOtherVector:vector];
}

+ (CGFloat)aVector:(DHVector2D *)aVector dotProductedByOtherVector:(DHVector2D *)oVector
{
    CGPoint p = [aVector coordinateExpression];
    CGPoint op = [oVector coordinateExpression];
    return p.x * op.x + p.y * op.y;
}

@end

Vector transformation

Vector transformation includes rotation and translation. Scaling is actually the product of the number of vectors.

translation

Vector translation refers to an operation in which the direction and size of a vector remain unchanged and only the starting point of the vector is changed. Obviously, how much has the starting point changed and how much should the ending point change?

So for a vector a= (x,y), if its starting point is translated to (x1,y1), then the ending point of vector a_is (x+x1,y+y1).

rotate

Here comes the point!

Vector rotation function can help us solve a lot of problems in drawing. We should learn abstract thinking, abstract some concrete problems into mathematical models, and abstract drawing into vector problems. In this way, the thinking will be clearer.

Vector rotation refers to the rotation of a vector in a direction (clockwise or counterclockwise) with its starting point as the center of the circle. If we want to rotate along any point, we will not discuss the calculation of matrix.

Note that when we calculate the vector rotation formula, we rotate based on the origin, so if we want to rotate any vector around its starting point, we can first move to the origin, rotate according to the specified rotation rules, and then move back.

In our common plane rectangular coordinate system, the rotation formula of a plane vector can be derived from the geometric meaning of triangular function.

Vector rotation formula I give directly, the demonstration process you can go online to search.

If there is a vector a_; = (x,y), the coordinate expression of clockwise rotation of theta along the origin is (x', y'), then there is

x′=x⋅cosθ+y⋅sinθ

y′=−x⋅sinθ+y⋅cosθ

Coordinate System Conversion

I don't know if you have noticed a problem so far, that is, all the formulaic coordinate systems of plane vectors we discussed above are Cartesian coordinate systems, i.e., the y axis is upward, while the coordinate system we draw is UIKit coordinate system, and the y axis is downward, if we use the above formulas directly. What's wrong with UIKit coordinates?

The answer is yes, there will be problems. But the problem is not too big. The problem of coordinate transformation can only occur in the operation of rotation. So if we want to use various formulas of plane vectors in UIKit, as long as we pay attention to the transformation of coordinate system when rotating vectors, the other operations have no effect. As for why, you can think for yourself.

Okay, so how do we do coordinate transformation in rotation?

Consider a Cartesian vector (vector in Cartesian coordinates) a_; = (10,0), which is a vector in exactly the same direction as the positive direction of the x-axis. If we rotate the vector counter-clockwise with PI 2, it is obvious that even if we do not use the formula, we know that the vector becomes a_; = (0,10), that is, it points to the positive direction of the y-axis (in the positive direction of the x-axis). The arrow of the vector in Cartesian coordinates goes up.

What happens if a_is moved to UIKit coordinates for the same operation? The coordinate expression of the vector will not change, but the arrow of the vector will go down after rotation, because the y axis of the UIKit coordinate system will go down in the positive direction.

Okay, now do you find out? In Cartesian coordinate system, a vector is rotated counterclockwise from (10,0) to (0,10). In UIKit coordinate system, a vector is rotated clockwise from (10,0) to (0,10). So if we want to use Cartesian vector in UIKit coordinate system, we only need to take the direction of rotation inversely when using the formula. All right.

More specifically, for counterclockwise rotation in UIKit coordinates, the formula should take the clockwise rotation formula in Cartesian coordinates; for clockwise rotation in UIKit coordinates, the formula should take the counterclockwise rotation formula in Cartesian coordinates.

We can pay attention to this point when programming.

Exciting Realization Links

category Statement

@interface DHVector2D (VectorTransforming)

/**
 *  Move the starting point to a point equal to the original vector
 *
 *  @param point Points to be moved to
 */
- (void)translationToPoint:(CGPoint)point;

/**
 *  Rotate a radian clockwise
 *
 *  @param radian The radian value to rotate
 */
- (void)rotateClockwiselyWithRadian:(CGFloat)radian;

/**
 *  Rotate an arc counterclockwise
 *
 *  @param radian The radian value to rotate
 */
- (void)rotateAntiClockwiselyWithRadian:(CGFloat)radian;

/**
 *  Reverse the vector
 */
- (void)reverse;

@end

category implementation

@implementation DHVector2D (VectorTransforming)

#pragma mark - Translation
// Move the starting point to a certain point
- (void)translationToPoint:(CGPoint)point
{
    _endPoint = CGPointMake(point.x - _startPoint.x + _endPoint.x, point.y - _startPoint.y + _endPoint.y);
    _startPoint = point;
}

#pragma mark - rotation

- (void)rotateWithCoordinateSystem:(DHVectorCoordinateSystem)coordinateSystem radian:(CGFloat)radian clockWisely:(BOOL)flag
{
    if (coordinateSystem == DHVectorCoordinateSystemOpenGL) {
        [self rotateInOpenGLSystemWithRadian:radian clockwisely:flag];
    } else {
        [self rotateInOpenGLSystemWithRadian:radian clockwisely:!flag];
    }
}

- (void)rotateInOpenGLSystemWithRadian:(CGFloat)radian clockwisely:(BOOL)flag
{
    if (flag) {
        // First, the vector is translated to the origin.
        CGPoint point = _startPoint;
        [self translationToPoint:CGPointMake(0, 0)];
        // Calculate the endpoint after rotating along the origin
        CGFloat x1 = _endPoint.x * cos(radian) + _endPoint.y * sin(radian);
        CGFloat y1 = -_endPoint.x * sin(radian) + _endPoint.y * cos(radian);

        _endPoint = CGPointMake(x1, y1);

        // Move startpoint back to its original location
        [self translationToPoint:point];
    } else {
        // First, the vector is translated to the origin.
        CGPoint point = _startPoint;
        [self translationToPoint:CGPointMake(0, 0)];
        // Calculate the endpoint after rotating along the origin
        CGFloat x1 = _endPoint.x * cos(radian) - _endPoint.y * sin(radian);
        CGFloat y1 = _endPoint.x * sin(radian) + _endPoint.y * cos(radian);

        _endPoint = CGPointMake(x1, y1);

        // Move startpoint back to its original location
        [self translationToPoint:point];
    }
}

// Clockwise
- (void)rotateClockwiselyWithRadian:(CGFloat)radian
{
    [self rotateWithCoordinateSystem:self.coordinateSystem radian:radian clockWisely:YES];
}

// Anti-clockwise
- (void)rotateAntiClockwiselyWithRadian:(CGFloat)radian
{
    [self rotateWithCoordinateSystem:self.coordinateSystem radian:radian clockWisely:NO];
}

- (void)reverse
{
    CGPoint startPoint = self.startPoint;
    self.startPoint = self.endPoint;
    self.endPoint = startPoint;
}

@end

Drawing Arbitrary Regular Polygons with Vectors

The various operations of our plane vectors are basically described here. Next, let's look at a practical example. We use the powerful function of plane vectors to quickly draw any regular polygon, that is, we can draw any length and any number of edges of the regular polygon.

When I began to talk about vector rotation, I said that we should learn to think abstractly and abstract this problem into a mathematical model. In fact, this problem can be regarded as the problem of vector rotation.

How did the idea come about? First of all, what problems do we need to solve to draw a regular polygon? The answer is simple. Find the coordinates of all vertices. Find out the coordinates of all vertices, and we can draw them using UIBezierPath+CAShapeLayer.

Now let's solve this problem: how to find the coordinates of all vertices of a regular polygon?

Draw a regular polygon randomly, find its center, and then connect it with the center to all the vertices. Did you find that these lines, like a vector whose starting point is in the center, are obtained by many rotations? Every time we rotate, we get the endpoint coordinate of a vector, not the vertex coordinate of a regular polygon, and the angle of each rotation is the same, so we can use for loop to control, so any polygon can be drawn.

The specific implementation algorithm is as follows:

  1. Determining the Side Number, Side Length and Center Point Coordinates of a Regular Polygon Based on Its Parameters
  2. Calculate the angle formed by the connection of two adjacent vertices and the center point (2pi edge number)
  3. Based on the angle and edge length obtained in the previous step, the vector length is calculated by using cosine theorem.
  4. Determine a vector, take the center of the regular polygon as the starting point, and get a vector whose length is the length calculated in the previous step.
  5. The vertex coordinates are obtained by rotating the angle obtained in the second step of each vector. These coordinates are put into the array.
  6. Drawing with UIBezierPath+CAShapeLayer based on the array obtained in step 5
#import "ViewController.h"
#import "DHVector2D.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Regular heptagon
    [self drawRegularPolygonAtCenter:self.view.center edgeCount:7 edgeLength:100];

}

- (void)drawRegularPolygonAtCenter:(CGPoint)center edgeCount:(int)edgeCount edgeLength:(CGFloat)length {
    // Angle formed by the connection of two adjacent vertices and central points
    CGFloat innerAngle = M_PI * 2 / edgeCount;
    // Calculate the coordinates of the vertex in the lower left corner (I don't use the cosine theorem here, cot is cotangent, see if you can see how my algorithm works here. hint: perpendicular to the side from the center point
    CGFloat x1 = - length/2;
    CGFloat cot = cos(innerAngle/2) / sin(innerAngle/2);
    CGFloat y1 = length/2 * cot;
    // Constructing Initial Vector
    DHVector2D * vector = [[DHVector2D alloc] initWithCoordinateExpression:CGPointMake(x1, y1)];
    [vector translationToPoint:center];
    NSMutableArray * vertexes = [NSMutableArray arrayWithCapacity:0];
    for (int i = 0; i < edgeCount; ++i) {


        NSValue * vertex = [NSValue valueWithCGPoint:vector.endPoint];
        [vertexes addObject:vertex];

        // Rotation vector
        [vector rotateClockwiselyWithRadian:innerAngle];
    }

    // Draw

    UIBezierPath * bezierPath = [UIBezierPath bezierPath];
    [vertexes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        CGPoint vertex = [obj CGPointValue];
        if (idx == 0) {
            [bezierPath moveToPoint:vertex];
        } else {
            [bezierPath addLineToPoint:vertex];
        }
    }];

    [bezierPath closePath];

    CAShapeLayer * shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = bezierPath.CGPath;
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    [self.view.layer addSublayer:shapeLayer];
}


@end

Run and see, a heptagon with a side length of 100 and a center point in the center of the screen is drawn like this. Is it very powerful?

summary

In this chapter, we introduce the basic algorithm of plane vector in mathematics, and show how to use it in UIKit coordinate system. Finally, we implement a practical example to show you how powerful vector is in drawing.

The second part of this topic (Skills) is over. I hope you can learn something. See you in the next part.

Posted by polywog on Sun, 14 Jul 2019 15:40:52 -0700