Box2d and Cocos2d on iOS issues with sprites and positioning - ios

I am creating new sprites/bodies when touching the screen:
-(void) addNewSpriteAtPosition:(CGPoint)pos
{
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position=[Helper toMeters:pos];
b2Body* body = world->CreateBody(&bodyDef);
b2CircleShape circle;
circle.m_radius = 30/PTM_RATIO;
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape=&circle;
fixtureDef.density=0.7f;
fixtureDef.friction=0.3f;
fixtureDef.restitution = 0.5;
body-> CreateFixture(&fixtureDef);
PhysicsSprite* sprite = [PhysicsSprite spriteWithFile:#"circle.png"];
[self addChild:sprite];
[sprite setPhysicsBody:body];
body->SetUserData((__bridge void*)sprite);
}
Here is my positioning helper:
+(b2Vec2) toMeters:(CGPoint)point
{
return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO);
}
PhysicsSprite is the typical one used with Box2D, but I'll include the relevant method:
-(CGAffineTransform) nodeToParentTransform
{
b2Vec2 pos = physicsBody->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
if (ignoreAnchorPointForPosition_)
{
x += anchorPointInPoints_.x;
y += anchorPointInPoints_.y;
}
float radians = physicsBody->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if (!CGPointEqualToPoint(anchorPointInPoints_, CGPointZero))
{
x += c * -anchorPointInPoints_.x + -s * -anchorPointInPoints_.y;
y += s * -anchorPointInPoints_.x + c * -anchorPointInPoints_.y;
}
self.position = CGPointMake(x, y);
// Rot, Translate Matrix
transform_ = CGAffineTransformMake(c, s, -s, c, x, y);
return transform_;
}
Now, I have two issues illustrated by the following two images, which show the debug draw with the sprites. Retina and non-retina versions:
Issue #1 - As you can see in both images, the further away from (0,0) the objects are, the sprite becomes further offset from the physics body.
Issue #2 - The red circle image files are 60x60 (retina), and the white circles are 30x30 (non-retina). Why are they sized differently on screen? Cocos2d should use points, not pixels, so shouldn't they be the same size on screen?

Instead of hardcoded size, use contentSize.
circle.m_radius = sprite.contentSize.width*0.5f/PTM_RATIO;
This works in all SD and Retina mode.
You can use this style to sync between box2d and cocos2d positions:
body->SetTransform([self toB2Meters:sprite.position], 0.0f); //box2d<---cocos2d
//OR
sprite.position = ccp(body->GetPosition().x * PTM_RATIO,
body->GetPosition().y * PTM_RATIO); //box2d--->cocos2d

Related

How can I track a point on a texture in OpenGL ES1?

In my iOS application I have a texture applied to a sphere rendered in OpenGLES1. The sphere can be rotated by the user. How can I track where a given point on the texture is in 2D space at any given time?
For example, given point (200, 200) on a texture that's 1000px x 1000px, I'd like to place a UIButton on top of my OpenGL view that tracks the point as the sphere is manipulated.
What's the best way to do this?
On my first attempt, I tried to use a color-picking technique where I have a separate sphere in an off-screen framebuffer that uses a black texture with a red square at point (200, 200). Then, I used glReadPixels() to track the position of the red square and I moved my button accordingly. Unfortunately, grabbing all the pixel data and iterating it 60 times a second just isn't possible for obvious performance reasons. I tried a number of ways to optimize this hack (eg: iterating only the red pixels, iterating every 4th red pixel, etc), but it just didn't prove to be reliable.
I'm an OpenGL noob, so I'd appreciate any guidance. Is there a better solution? Thanks!
I think it's easier to keep track of where your ball is instead of searching for it with pixels. Then just have a couple of functions to translate your ball's coordinates to your view's coordinates (and back), then set your subview's center to the translated coordinates.
CGPoint translatePointFromGLCoordinatesToUIView(CGPoint coordinates, UIView *myGLView){
//if your drawing coordinates were between (horizontal {-1.0 -> 1.0} vertical {-1 -> 1})
CGFloat leftMostGLCoord = -1;
CGFloat rightMostGLCoord = 1;
CGFloat bottomMostGLCoord = -1;
CGFloat topMostGLCoord = 1;
CGPoint scale;
scale.x = (rightMostGLCoord - leftMostGLCoord) / myGLView.bounds.size.width;
scale.y = (topMostGLCoord - bottomMostGLCoord) / myGLView.bounds.size.height;
coordinates.x -= leftMostGLCoord;
coordinates.y -= bottomMostGLCoord;
CGPoint translatedPoint;
translatedPoint.x = coordinates.x / scale.x;
translatedPoint.y =coordinates.y / scale.y;
//flip y for iOS coordinates
translatedPoint.y = myGLView.bounds.size.height - translatedPoint.y;
return translatedPoint;
}
CGPoint translatePointFromUIViewToGLCoordinates(CGPoint pointInView, UIView *myGLView){
//if your drawing coordinates were between (horizontal {-1.0 -> 1.0} vertical {-1 -> 1})
CGFloat leftMostGLCoord = -1;
CGFloat rightMostGLCoord = 1;
CGFloat bottomMostGLCoord = -1;
CGFloat topMostGLCoord = 1;
CGPoint scale;
scale.x = (rightMostGLCoord - leftMostGLCoord) / myGLView.bounds.size.width;
scale.y = (topMostGLCoord - bottomMostGLCoord) / myGLView.bounds.size.height;
//flip y for iOS coordinates
pointInView.y = myGLView.bounds.size.height - pointInView.y;
CGPoint translatedPoint;
translatedPoint.x = leftMostGLCoord + (pointInView.x * scale.x);
translatedPoint.y = bottomMostGLCoord + (pointInView.y * scale.y);
return translatedPoint;
}
In my app I choose to use the iOS coordinate system for my drawing too. I just apply a projection matrix to my whole glkView the reconciles the coordinate system.
static GLKMatrix4 GLKMatrix4MakeIOSCoordsWithSize(CGSize screenSize){
GLKMatrix4 matrix4 = GLKMatrix4MakeScale(
2.0 / screenSize.width,
-2.0 / screenSize.height,
1.0);
matrix4 = GLKMatrix4Translate(matrix4,-screenSize.width / 2.0, -screenSize.height / 2.0, 0);
return matrix4;
}
This way you don't have to translate anything.

Cocos2d v3: How do you draw an arc?

I want to draw a filled arc like this:
CCDrawNode only contain methods to draw a circle, a polygon or a line – but no arc.
To be clear, I'm wanting to be able to generate the arc at runtime, with an arbitrary radius and arc angle.
My Question:
Is there a way to have Cocos2d draw an arc, or would I have to do it myself with OpenGL?
I guess you can use the CCProgressNode, and by setting the sprite to an orange circle, you can draw an arc by setting the progress property.
Now, whether you can set the sprite as a vectorized circle that you can just scale, I am not really sure.
Personally, I'd suggest you add the drawing arc code to CCDrawNode, and submit a PR.
tested using cocos2dx v3.8, codes from DrawNode::drawSolidCircle
DrawNode* Pie::drawPie(const Vec2& center, float radius, float startAngle, float endAngle, unsigned int segments, float scaleX, float scaleY, const Color4F &color){
segments++;
auto draw = DrawNode::create();
const float coef = (endAngle - startAngle) / segments;
Vec2 *vertices = new (std::nothrow) Vec2[segments];
if (!vertices)
return nullptr;
for (unsigned int i = 0; i < segments - 1; i++)
{
float rads = i*coef;
GLfloat j = radius * cosf(rads + startAngle) * scaleX + center.x;
GLfloat k = radius * sinf(rads + startAngle) * scaleY + center.y;
vertices[i].x = j;
vertices[i].y = k;
}
vertices[segments - 1].x = center.x;
vertices[segments - 1].y = center.y;
draw->drawSolidPoly(vertices, segments, color);
CC_SAFE_DELETE_ARRAY(vertices);
return draw;
}
call it like
auto pie = Pie::drawPie(_visibleSize / 2, _visibleSize.width / 4, 0, 1, 999, 1, 1, Color4F::BLUE);

Using OpenGL ES 2.0 with iOS, how do I draw a cylinder between two points?

I am given two GLKVector3's representing the start and end points of the cylinder. Using these points and the radius, I need to build and render a cylinder. I can build a cylinder with the correct distance between the points but in a fixed direction (currently always in the y (0, 1, 0) up direction). I am not sure what kind of calculations I need to make to get the cylinder on the correct plane between the two points so that a line would run through the two end points. I am thinking there is some sort of calculations I can apply as I create my vertex data with the direction vector, or angle, that will create the cylinder pointing the correct direction. Does anyone have an algorithm, or know of one, that will help?
Are you drawing more than one of these cylinders? Or ever drawing it in a different position? If so, using the algorithm from the awesome article is a not-so-awesome idea. Every time you upload geometry data to the GPU, you incur a performance cost.
A better approach is to calculate the geometry for a single basic cylinder once — say, one with unit radius and height — and stuff that vertex data into a VBO. Then, when you draw, use a model-to-world transformation matrix to scale (independently in radius and length if needed) and rotate the cylinder into place. This way, the only new data that gets sent to the GPU with each draw call is a 4x4 matrix instead of all the vertex data for whatever polycount of cylinder you're drawing.
Check this awesome article; it's dated but after adapting the algorithm, it works like a charm. One tip, OpenGL ES 2.0 only supports triangles so instead of using GL_QUAD_STRIP as the method does, use GL_TRIANGLE_STRIP instead and the result is identical. The site also contains a bunch of other useful information regarding OpenGL geometries.
See code below for solution. Self represents the mesh and contains the vertices, indices, and such.
- (instancetype)initWithOriginRadius:(CGFloat)originRadius
atOriginPoint:(GLKVector3)originPoint
andEndRadius:(CGFloat)endRadius
atEndPoint:(GLKVector3)endPoint
withPrecision:(NSInteger)precision
andColor:(GLKVector4)color
{
self = [super init];
if (self) {
// normal pointing from origin point to end point
GLKVector3 normal = GLKVector3Make(originPoint.x - endPoint.x,
originPoint.y - endPoint.y,
originPoint.z - endPoint.z);
// create two perpendicular vectors - perp and q
GLKVector3 perp = normal;
if (normal.x == 0 && normal.z == 0) {
perp.x += 1;
} else {
perp.y += 1;
}
// cross product
GLKVector3 q = GLKVector3CrossProduct(perp, normal);
perp = GLKVector3CrossProduct(normal, q);
// normalize vectors
perp = GLKVector3Normalize(perp);
q = GLKVector3Normalize(q);
// calculate vertices
CGFloat twoPi = 2 * PI;
NSInteger index = 0;
for (NSInteger i = 0; i < precision + 1; i++) {
CGFloat theta = ((CGFloat) i) / precision * twoPi; // go around circle and get points
// normals
normal.x = cosf(theta) * perp.x + sinf(theta) * q.x;
normal.y = cosf(theta) * perp.y + sinf(theta) * q.y;
normal.z = cosf(theta) * perp.z + sinf(theta) * q.z;
AGLKMeshVertex meshVertex;
AGLKMeshVertexDynamic colorVertex;
// top vertex
meshVertex.position.x = endPoint.x + endRadius * normal.x;
meshVertex.position.y = endPoint.y + endRadius * normal.y;
meshVertex.position.z = endPoint.z + endRadius * normal.z;
meshVertex.normal = normal;
meshVertex.originalColor = color;
// append vertex
[self appendVertex:meshVertex];
// append color vertex
colorVertex.colors = color;
[self appendColorVertex:colorVertex];
// append index
[self appendIndex:index++];
// bottom vertex
meshVertex.position.x = originPoint.x + originRadius * normal.x;
meshVertex.position.y = originPoint.y + originRadius * normal.y;
meshVertex.position.z = originPoint.z + originRadius * normal.z;
meshVertex.normal = normal;
meshVertex.originalColor = color;
// append vertex
[self appendVertex:meshVertex];
// append color vertex
[self appendColorVertex:colorVertex];
// append index
[self appendIndex:index++];
}
// draw command
[self appendCommand:GL_TRIANGLE_STRIP firstIndex:0 numberOfIndices:self.numberOfIndices materialName:#""];
}
return self;
}

Tile to CGPoint conversion with Retina display

I have a project that uses a tilemap. I have a separate tilemap for low-res (29x29 Tilesize) and high-res (58x58). I have these methods to calculate tileCoord to position and back again.
- (CGPoint)tileCoordForPosition:(CGPoint)position {
int x = position.x / _tileMap.tileSize.width;
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
return ccp(x, y);
}
- (CGPoint)positionForTileCoord:(CGPoint)tileCoord {
int x = (tileCoord.x * _tileMap.tileSize.width) + _tileMap.tileSize.width/2;
int y = (_tileMap.mapSize.height * _tileMap.tileSize.height) - (tileCoord.y * _tileMap.tileSize.height) - _tileMap.tileSize.height/2;
return ccp(x, y);
}
I got this from RayWenderLich and I do honeslty not understand how it works, and why it has to be so complicated. But this doesn't work when I use retina tilemaps, only on 480x320. Can someone clever come up with a way to make this work for HD? Does not have to work on low-res either, I do not plan on supporting sub-iOS 7.
I want the output to be in the low-res coordinate scale tho, as you might know, cocos2d does the resizing to HD for you. (By multiplying by two)
i think this will work
- (CGPoint)tileCoordForPosition:(CGPoint)position {
    int x = position.x/29;
    int y = ((11*29)-position.y) / 29;
    
    return ccp(x, y);
}
- (CGPoint)positionForTileCoord:(CGPoint)tileCoord {
    double x = tileCoord.x * 29 + 14.5;
    double y = (11*29) - (tileCoord.y * 29) - 14.5;
    return ccp(x, y);
}
Here you're trying to compute your map X coordinate:
int x = position.x / _tileMap.tileSize.width;
The problem here is that (as of v0.99.5-rc0, cocos2d generally uses points for positions, but CCTMXTiledMap always uses pixels for tileSize. On a low-res device, 1 point = 1 pixel, but on a Retina device, 1 point = 2 pixels. Thus on a Retina device, you need to multiply by 2.
You can use the CC_CONTENT_SCALE_FACTOR() macro to fix this:
int x = CC_CONTENT_SCALE_FACTOR() * position.x / _tileMap.tileSize.width;
Here you're trying to compute yoru map Y coordinate:
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
The extra math here is trying to account for the difference between Cocos2D's normal coordinate system and your map's flipped coordinate system. In standard Cartesian coordinates, the origin is at the lower left and Y coordinates increase as you move up. In a flipped coordinate system, the origin is at the upper left and Y coordinates increase as you move down. Thus you must subtract your position's Y coordinate from the height of the map (in scene units, which are points) to flip it to map coordinates.
The problem again is that _tileMap.tileSize is in pixels, not points. You can again fix that by using CC_CONTENT_SCALE_FACTOR():
CGFloat tileHeight = _tileMap.tileSize.height / CC_CONTENT_SCALE_FACTOR();
int y = ((_tileMap.mapSize.height * tileHeight) - position.y) / tileHeight;

How do you change the edges of a ground fixture in a box2d ground body

During game play, I would like to be able to change the ground edges in a Box2D world. I have created a ground body and then I am adding a ground fixture to the body. For example, the following code will create a flat ground 20 pixels above the bottom of the screen in my Box2D world:
b2BodyDef groundBodyDef;
groundBodyDef.type = b2_staticBody;
groundBodyDef.position.Set(0, 0);
groundBody = world->CreateBody(&groundBodyDef);
b2PolygonShape groundShape;
b2FixtureDef groundFixtureDef;
groundFixtureDef.shape = &groundShape;
groundFixtureDef.density = 0.0;
CGSize screenSize = [CCDirector sharedDirector].winSize;
int num = 2;
b2Vec2 verts[] = {
b2Vec2(-screenSize.width / 100.0, 20.0 / 100.0),
b2Vec2(screenSize.width / 100.0, 20.0 / 100.0)
};
for(int i = 0; i < num - 1; ++i) {
b2Vec2 offset = b2Vec2(screenSize.width/2/PTM_RATIO,
20.0/2/PTM_RATIO);
b2Vec2 left = verts[i] + offset;
b2Vec2 right = verts[i+1] + offset;
groundShape.SetAsEdge(left, right);
groundBody->CreateFixture(&groundFixtureDef);
}
Suppose I need change the verts array to have a different ground fixture definition? For example, is it possible to dynamically raise the ground by 50 pixels between moves of a player? Do I need to delete the entire ground body and recreate the ground body and fixture, or can I just delete or modify the existing ground fixture?
I found that it worked to destroy the current groundBody followed by the creation of a new groundBody and ground fixture works fine. I don't know if this is best for performance, but it works like I wanted it to.
So basically, at the appropriate time during my game, I do the following:
world->DestroyBody(groundBody);
Then I re-execute the code I showed in the problem statement above, substituting the 20.0 pixel value with the 50.0 pixel value in the verts array as follows:
b2Vec2 verts[] = {
b2Vec2(-screenSize.width / 100.0, 50.0 / 100.0),
b2Vec2(screenSize.width / 100.0, 50.0 / 100.0)
}
Change b2PolygonShape to b2EdgeShape.
CGSize s = [[CCDirector sharedDirector] winSize];
b2EdgeShape groundBox;
// bottom
groundBox.Set(b2Vec2(0.0f,0.0f), b2Vec2(s.width/PTM_RATIO,0.0f));
groundBody->CreateFixture(&groundBox,0);
You can't change fixture definition at runtime. So use different fixture and switch that body at run time.

Resources