Rotate a Sprite on a bezier path with touch - Cocos2D/Box2D

前端 未结 2 1867
说谎
说谎 2021-01-14 06:21

I have an arrow that I rotate with touch. I was wondering if it was possible to rotate the arrow on a curved line? I\'ve done some research and I think it is called a bezier

2条回答
  •  北海茫月
    2021-01-14 07:12

    Have faced with same problem couple of days. Most of links in this answer is broken, so I have found material here and here and made this code. Works like magic. Hope it will help someone.

    Small description: I have object (self) witch rotates by finger around another object (self.target), and i have some animated sprites like guides of self movement, which rotates around self.target by bezier function. algoritm is quite fast, i have permanent initialization of 100+ guides and it works without CPU overload.

    /**
     Each bezier curve is an array with 8 floats, x1, y1, x2, y2, x3, y3, x4, y4., where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.
     @note adapted for xCode by Valentine Konov valentine\@konov.su 2013
    
     @return a array of objects that represent bezier curves which approximate the circular arc centered at the origin.
     @param  startAngle to endAngle (radians) with the specified radius.
     */
    
    -(NSArray*)createArcWithRadius:(float)radius_ withStartAngle:(float)startAngle_ withEndAngle:(float)endAngle_;
    {
    //    OMLog(@"radius:%.2f startAngle:%.2f endAngle:%.2f",radius_,startAngle_,endAngle_);
        // normalize startAngle, endAngle to [-2PI, 2PI]
    
        float twoPI = M_PI * 2;
        float startAngle = startAngle_;
        float endAngle = endAngle_;
        //    float startAngle = fmodf(startAngle_,twoPI);
        //    float endAngle = fmodf(endAngle_,twoPI);
    
        // Compute the sequence of arc curves, up to PI/2 at a time.  Total arc angle
        // is less than 2PI.
    
        NSMutableArray* curves = [NSMutableArray array];
        float piOverTwo = M_PI / 2.0;
        float sgn = (startAngle < endAngle) ? 1 : -1;
    
        float a1 = startAngle;
        for (float totalAngle = fminf(twoPI, fabsf(endAngle - startAngle)); totalAngle > 0.00001f /*FLT_EPSILON*/; nil) {
            float a2 = a1 + sgn * min(totalAngle, piOverTwo);
            [curves addObject: [self createSmallArc:radius_ a1:a1 a2:a2]];
            totalAngle -= fabsf(a2 - a1);
            a1 = a2;
        }
        return curves;
    }
    
    /**
     Cubic bezier approximation of a circular arc centered at the origin,
    
     This algorithm is based on the approach described in:
     A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa,"
     Information Technology and Control, 35(4), 2006 pp. 371-378.
     @note adapted for xCode by Valentine Konov valentine\@konov.su 2013
    
     @param from (radians) a1 to a2, where a2-a1 < pi/2
    
     @return an array with 8 floats, x1, y1, x2, y2, x3, y3, x4, y4. where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.
    
     */
    -(NSArray*)createSmallArc:(float)r a1:(float)a1 a2:(float)a2
    {
        // Compute all four points for an arc that subtends the same total angle
        // but is centered on the X-axis
    
        float a = (a2 - a1) / 2.0; //
    
        float x4 = r * cosf(a);
        float y4 = r * sinf(a);
        float x1 = x4;
        float y1 = -y4;
    
        float k = 0.5522847498;
        float f = k * tan(a);
    
        float x2 = x1 + f * y4;
        float y2 = y1 + f * x4;
        float x3 = x2;
        float y3 = -y2;
    
        // Find the arc points actual locations by computing x1,y1 and x4,y4
        // and rotating the control points by a + a1
    
        float ar = a + a1;
        float cos_ar = cosf(ar);
        float sin_ar = sinf(ar);
    
    
        return [NSArray arrayWithObjects:                           //
            [NSNumber numberWithFloat:(r * cosf(a1))],              //startPoint.x
            [NSNumber numberWithFloat:(r * sinf(a1))],              //startPoint.y
            [NSNumber numberWithFloat:(x2 * cos_ar - y2 * sin_ar)], //ctrlPoint1.x
            [NSNumber numberWithFloat:(x2 * sin_ar + y2 * cos_ar)], //ctrlPoint1.y
            [NSNumber numberWithFloat:(x3 * cos_ar - y3 * sin_ar)], //ctrlPoint2.x
            [NSNumber numberWithFloat:(x3 * sin_ar + y3 * cos_ar)], //ctrlPoint2.y
            [NSNumber numberWithFloat:(r * cosf(a2))],              //endPoint.x
            [NSNumber numberWithFloat:(r * sinf(a2))],              //endPoint.y
            nil];
    }
    
    /**
     Bezier approximation example
    
     @note adapted for xCode by Valentine Konov valentine\@konov.su 2013
    
     @param inSprite_ is sprite, angle_ signed angle radiants
    
     @return CCSequence of [CCSpawns of (CCBezierTo and CCRotateBy)]
    
     */
    
    -(id)calcBezierCircle:(CCSprite*)inSprite_ withAngle:(float)angle_
    {
        double speed = 100; //points per second
    
        CGPoint positionOffset = ccpSub(((CCNode*)self.target).position, self.position);
        //((CCNode*)self.target).position is circle center
        double startAngle = [self calcAngle:inSprite_.position ownerRelated:false];
        while (startAngle<0) startAngle += 2*M_PI;
        while (startAngle>=2*M_PI) startAngle -= 2*M_PI;
        double endAngle = startAngle + angle_;
        float radius = [self calcRadius];
    
        NSArray* curves = [self createArcWithRadius:radius withStartAngle:startAngle withEndAngle:endAngle];
        NSMutableArray* bezierActions = [NSMutableArray array];
        for (NSArray* curve in curves) {
            CGPoint startPoint =    ccpAdd(ccp([[curve objectAtIndex:0] floatValue], [[curve objectAtIndex:1] floatValue]), positionOffset);
            CGPoint controlPoint1 = ccpAdd(ccp([[curve objectAtIndex:2] floatValue], [[curve objectAtIndex:3] floatValue]), positionOffset);
            CGPoint controlPoint2 = ccpAdd(ccp([[curve objectAtIndex:4] floatValue], [[curve objectAtIndex:5] floatValue]), positionOffset);
            CGPoint endPoint =      ccpAdd(ccp([[curve objectAtIndex:6] floatValue], [[curve objectAtIndex:7] floatValue]), positionOffset);
    
            ccBezierConfig bezier;
            bezier.controlPoint_1 = controlPoint1;
            bezier.controlPoint_2 = controlPoint2;
            bezier.endPosition =endPoint;
    
            float bezierAngle = ccpAngleSigned(ccpSub(startPoint, positionOffset), ccpSub(endPoint, positionOffset));
            float bezierDuration = radius*fabsf(bezierAngle)/speed;
            id bezierTo = [CCBezierTo actionWithDuration:bezierDuration bezier:bezier];
            id rotateBy = [CCRotateBy actionWithDuration:bezierDuration angle:CC_RADIANS_TO_DEGREES(-bezierAngle)];
            CCAction * bezierToAndRotateBy = [CCSpawn actions:bezierTo, rotateBy, nil];
    
            [bezierActions addObject:bezierToAndRotateBy];
        }
        if ([bezierActions count]<1) {
            return nil;
        }
        return [CCSequence actionWithArray:bezierActions];
    }
    
    
    /**
     Calculates angle
     @param position_ current position of sprite on sircle, ownerRelated boolean, wich is startPoint is {1,0} or owner.position
     @return angle (radiant)
     */
    -(float)calcAngle:(CGPoint)position_ ownerRelated:(bool)ownerRelated {
        if (ownerRelated) {
            CGPoint v1 = ccpSub(((CCNode*)self.target).position, self.position);
            CGPoint v2 = ccpSub(ccpSub(((CCNode*)self.target).position, self.position),position_);
            return ccpAngleSigned(v1, v2);
        }
        else {
            CGPoint v1 = ccp([self calcRadius], 0.0f);
            CGPoint v2 = ccpSub(position_,ccpSub(((CCNode*)self.target).position, self.position));
            return ccpAngleSigned(v1, v2);
        }
    }
    
    /**
     Calculates radius
     @return radius
     */
    -(float)calcRadius;
    {
        return sqrt(pow(self.position.x-((CCSprite*)self.target).position.x, 2)+pow(self.position.y-((CCSprite*)self.target).position.y, 2));
    }
    

提交回复
热议问题