Convert rotation angle in world space to local space cocos2d - cocos2d-iphone

Is there a simple way (ie an api) that converts an angle of rotation in world space to local space of a CCNode? I know the angle of rotation a node needs to have on the screen, but the node is nested deeply in node hierarchy and I would like to set its angle so that it matches what I want. If there's no api, what CC api calls should I make? Something like this:
CCNode * myLocalNode;
float myLocalAngle = CCAngleConvertFromWorldToNode(myLocalNode,myWorldAngle);
myLocalNode -> setRotation(myLocalAngle);

I think the best way to do is to get two reference points, one representing the origin and the other representing the rotation vector. Convert these 2 points to node space, measure the vector between them (subtraction) and convert that to an angle. Here are 2 methods that should be a category of CCNode (note: I refer to self), one to world and the other to node.
-(float) convertRotationToWorldSpace:(float)rotation
{
CGPoint rot = ccpForAngle(-CC_DEGREES_TO_RADIANS(rotation));
CGPoint worldPt = [self convertToWorldSpace:rot];
CGPoint worldOriginPt = [self convertToWorldSpace:CGPointZero];
CGPoint worldVec = ccpSub(worldPt, worldOriginPt);
return -CC_RADIANS_TO_DEGREES(ccpToAngle(worldVec));
}
-(float) convertRotationToNodeSpace:(float)rotation
{
CGPoint rot = ccpForAngle(-CC_DEGREES_TO_RADIANS(rotation));
CGPoint nodePt = [self convertToNodeSpace:rot];
CGPoint nodeOriginPt = [self convertToNodeSpace:CGPointZero];
CGPoint nodeVec = ccpSub(nodePt, nodeOriginPt);
return -CC_RADIANS_TO_DEGREES(ccpToAngle(nodeVec));
}

Related

Cocos2d 3.0 Chipmunk with gravity: sprite position doesn't change

Porting my game from Cocos2d v2 to v3 I don't know when the sprites go out of screen.
In v2 my solution was:
-(void) update:(ccTime) delta
{
// Should use a fixed size step based on the animation interval.
int steps = 2;
CGFloat dt = [[CCDirector sharedDirector] animationInterval]/(CGFloat)steps;
for(int i=0; i<steps; i++){
cpSpaceStep(space_, dt);
}
if (mySprite.getPhysicsBody->p.y > 500)
[mySprite resetPosition];
}
now with Cocos2d v3 mySprite.physicsNode.position doesn't change through the time.
Any idea or link with some example?
Thanks.
physicsNode.position doesn't change with time because it uses its parent sprite coordinate space, not the global coordinate space.
You can get the global position of any node, considering the Anchor Point, using this:
CGPoint worldPos = [node convertToWorldSpaceAR:CGPointZero];
After that you can easily convert it to any other node space if necessary (like your level, maybe) using:
CGPoint position = [_levelNode convertToNodeSpaceAR:worldPos];
But beware that you shouldn't hardcode the screen size on your code, as it varies for each device. You can use instead:
CGSize viewSize = [[CCDirector sharedDirector] viewSize];

Camera following the touched body

I need to have the Cocos2d camera follow a sprite (attached to a Box2D body) that the user is touching on the screen. As the user is dragging the player around, I need it to be able to go to other parts of the world. This has to be through touch, and not automatic scrolling.
I tried several approaches based on tutorials but nothing seem to address this issue. For example the solution offered here Move CCCamera with the ccTouchesMoved method? (cocos2d,iphone) by #Michael Fredrickson has the entire layer move, but when it moves, the sprites / bodies on the screen have unmatched coordinations and when I test to see if they're touched, the if(fixture->TestPoint(locationWorld)) fails.
I also looked at the tutorials here http://www.learn-cocos2d.com/2012/12/ways-scrolling-cocos2d-explained/ but this also isn't what I'm looking for.
Any help would be greatly appreciated.
EDIT:
I'm accepting Liolik's answer below because it put me on the right track. The last piece of the puzzle, though, is to make the value received from the getPoint method an instance variable, and deduce it from locationWorld which I'm doing the TestPoint against. Like this:
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
b2Vec2 diff = b2Vec2(difference.x, difference.y);
for (b2Body* b = _world->GetBodyList(); b; b = b->GetNext()) {
b2Fixture* f = b->GetFixtureList();
while(f != NULL) {
if(f->TestPoint(locationWorld-diff)) {
b2MouseJointDef def;
def.bodyA = _groundBody;
def.bodyB = b;
def.target = locationWorld-diff;
def.maxForce = 9999999.0f * b->GetMass();
_mouseJoint = (b2MouseJoint*)_world->CreateJoint(&def);
b->SetAwake(true);
}
f = f->GetNext();
}
}
in update function :
CGPoint direction = [self getPoint:myBody->GetPosition()];
[self setPosition:direction];
- (CGPoint)getPoint:(b2Vec2)vec
{
CGSize screen = [[CCDirector sharedDirector] winSize];
float x = vec.x * PTM_RATIO;
float y = vec.y * PTM_RATIO;
x = MAX(x, screen.width/2);
y = MAX(y, screen.height/2);
float _x = area.width - (screen.width/2);
float _y = area.height - (screen.height/2);
x = MIN(x, _x);
y = MIN(y, _y);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screen.width/2, screen.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
return difference;
}
So if i understand correctly, when the sprite is inside of the middle of the screen, the background is stationary and the sprite follows your finger, but when you scroll toward the edge, the camera starts to pan?
I had something roughly similar in my game Star Digger where there's a ship in the middle of the screen on its own layer that has to fly around the world, and had the same problem when the ship fired bullets into the main world layer.
heres what I did:
float thresholdMinX = winSize*1/3;
float thresholdMaxX = winSize*2/3;
if(touch.x > thresholdMaxX) //scrolling right
{
self.x += touch.x - thresholdMaxX;
}
else if(touchX < thresholdMinX)
{
self.x += thresholdMinX - touchX;
}
else
{
sprite.position = touch;
}
CGPoint spritePointInWorld = ccp(sprite.x - self.x, sprite.y - self.y);
then every time you calculate collisions, you need to recompute the sprites "actual" position in the world, which is its screen position minus the worlds offset, instead of the sprites screen position.

Rotating CCSprite thats an equilateral triangle

I have an CCSprite thats an equilateral triangle. I want to rotate the triangle in 60 degree increments holding its position.
The sprite is 126x110 (not square) setting the sprites rotation property in 60 degree increments changes the position of the sprite. How can i keep the sprite appear stationary at each rotation?
A bit more about this the center of the rectangle IS NOT the center of the sprite. So there is some adjustment needed when the rotation is needed to keep the center of the triangle appearing centered.
I think i came up with a long answer that needs to be approved..
// collect points.
self.point1 = CGPointMake(CGRectGetWidth(self.tile.boundingBox)/2, 0);
self.point2 = CGPointMake(CGRectGetWidth(self.tile.boundingBox), CGRectGetHeight(self.tile.boundingBox));
self.point3 = CGPointMake(0, CGRectGetHeight(self.tile.boundingBox));
// calculcate the mid point.
float midPointX = floor((self.point1.x + self.point2.x + self.point3.x)/3.0);
float midPointY = floor((self.point1.y + self.point2.y + self.point3.y)/3.0);
// stash the center of the triangle
self.triangleCenter = CGPointMake(midPointX, midPointY);
Then figure out new location of the center point based on rotation.. And animate there. (Sorry about hard coded center of the screen this was a rough test).
-(void) rotateToAngleAboutCenter:(float)angle {
// stash old values.
CGPoint oldPosition = self.tile.position;
float oldRotation = self.tile.rotation;
// reset the rotation
self.tile.rotation = 0;
// this is hard coded here currently the center of the screen.
self.tile.position = ccp(512, 384);
// figure out where our center point will be when we are rotated about the center.
CGPoint point = ccpRotateByAngle(self.triangleCenter, [self.tile anchorPointInPoints],-CC_DEGREES_TO_RADIANS(angle));
// convert the ppoint to local space.
point = [self.tile convertToWorldSpace:point];
point = [self convertToNodeSpace:point];
// reset the rotation and position.
self.tile.rotation = oldRotation;
self.tile.position = oldPosition;
// animate to new rotation/position.
CCMoveTo* moveTo = [CCMoveTo actionWithDuration:.25 position:point];
CCRotateTo* rotateTo = [CCRotateTo actionWithDuration:.25 angle:angle];
[self.tile runAction:moveTo];
[self.tile runAction:rotateTo];
}

How to move a sprite at a certain angle with a joystick

Hi I have finally made a working joystick in cocos2d. I am able to rotate a sprite to the exact angle that the joystick thumb, or cap, is 'pointing'. However, I am unable to move the sprite in that same direction. Is there an easy way to move the sprite with the way I have the rotating code set up? Also is there a way to keep it moving if your thumb is still pressed, but not moving the joystick?. PS this code is all within the TouchesMoved method. PPS. the cap is the thumb, the pad is the joystick background, and the Sprite2 is the sprite that I want to move. (95, 95) is the center of the pad sprite.
if(capSprite.position.x>=padSprite.position.x){
id a3 = [CCFlipX actionWithFlipX:NO];
[sprite2 runAction:a3];
}
if(capSprite.position.x<=padSprite.position.x){
id a4 = [CCFlipX actionWithFlipX:YES];
[sprite2 runAction:a4];
}
CGPoint pos1 = ccp(95, 95);
CGPoint pos2 = ccp(capSprite.position.x, capSprite.position.y);
int offX = pos2.x-pos1.x;
int offY = pos2.y-pos1.y;
float angleRadians = atanf((float)offY/(float)offX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float theAngle = -1 * angleDegrees;
sprite2.rotation = theAngle;
I'm not familiar with cocos2d but I had a quick look at the documentation and this sample might be of use to you:
if keys[key.UP]:
self.target.acceleration = (200 * rotation_x, 200 * rotation_y)
I had written a long explanation answering your second question but I believe this "self.target.acceleration" solves that too. You can read more at the cocos2d API documentation.
What I generally do is get the angle, convert it to a CGPoint with ccpForAngle(float) and then multiply the CGPoint by a value:
float angle = whatever;
CGPoint anglePoint = ccpForAngle(angle);
// You will need to play with the mult value
angle = ccpMult(angle, 2.5);
// This also works with box2D or probably Chipmunk.
sprite.position = angle;

How to change the particle angle in Cocos2D

Now I met a new question. How to modify every particle's angle to make it toward the center. Just like the images:
Image 1: normal particles effecing:
Image 2: which I need:
Image 2: which I need http://tinypic.com/images/404.gif
How about this code? You need to override CCParticleSystemQuad update: or updateQuadWithParticle:newPosition: method for specify the rotation of the particles. CCParticleSystemPoint can't rotate particles.
#interface MyParticleSystem : CCParticleSystemQuad
#end
#implementation MyParticleSystem
- (void)updateQuadWithParticle:(tCCParticle*)particle newPosition:(CGPoint)pos
{
particle->rotation = ccpToAngle(particle->pos) * 180.0f / M_PI;
[super updateQuadWithParticle:particle newPosition:pos];
}
#end
In order to turn particles towards their direction of movement (in your case: towards the center), you can do the following:
Add the oldPos property to the particle tCCParticle struct in CCParticleSystem.h
Initialize the oldPos property with the initial particle position in initParticle: in CCParticleSystem.m
Update the oldPos property with the current particle position in update: in CCParticleSystem.m before the new position is computed. I do this in line 512 immediately after checking whether the particle is still alive.
Override CCParticleSystemQuad as suggested by Kazuki:
- (void)updateQuadWithParticle:(tCCParticle *)particle
newPosition:(CGPoint)pos
{
CGPoint direction = ccpSub(particle->pos, particle->oldPos);
CGPoint n = ccpNormalize(direction);
CGFloat a = -CC_RADIANS_TO_DEGREES(ccpToAngle(n) - M_PI_2);
particle->rotation = a;
[super updateQuadWithParticle:particle newPosition:pos];
}