Cannot figure out correct vertexZ in Isometric Tiled maps when creating sprites - ios

I have a 50 X 50 isometric Tiled Map with base tile : 64 X 32.
I am using this function to create a sprite and add to a particular tile dynamically.
-(void)addTile:(NSString *)tileName AtPos:(CGPoint)tilePos onTileMap:(CCTMXTiledMap *)tileMap
{
CCTMXLayer *floorLayer=[tileMap layerNamed:#"FloorLayer"];
NSAssert(floorLayer !=nil, #"Ground layer not found!");
CGPoint tilePositionOnMap = [floorLayer positionAt:tilePos];
CCSprite *addedTile = [[CCSprite alloc] initWithFile:tileName];
addedTile.anchorPoint = CGPointMake(0, 0);
addedTile.position = tilePositionOnMap;
addedTile.vertexZ = [self calculateVertexZ:tilePos tileMap:tileMap];
[tileMap addChild:addedTile];
}
Floor layer is the only layer in my Tiled map and I have added the property cc_vertexz = -1000 to this layer.
I took the calculateVertexZ method from the KnightFight project. Based on the tile coordinates on isometric map it'll calculate the vertexZ and once you see the map it seems to make sense too.
-(float) calculateVertexZ:(CGPoint)tilePos tileMap:(CCTMXTiledMap*)tileMap
{
float lowestZ = -(tileMap.mapSize.width + tileMap.mapSize.height);
float currentZ = tilePos.x + tilePos.y;
return (lowestZ + currentZ + 1);
}
Now this is the code which i'm writing in the -init of my HelloWorldLayer of template cocos2d-2 project. -
self.myTileMap = [CCTMXTiledMap tiledMapWithTMXFile:#"IsometricMap.tmx"];
[self addChild:self.myTileMap z:-100 tag:TileMapNode];
[self addTile:#"walls-02.png" AtPos:CGPointMake(0, 0) onTileMap:self.myTileMap];
[self addTile:#"walls-02.png" AtPos:CGPointMake(0, 1) onTileMap:self.myTileMap];
[self addTile:#"walls-02.png" AtPos:CGPointMake(4, 1) onTileMap:self.myTileMap];
[self addTile:#"walls-02.png" AtPos:CGPointMake(4, 0) onTileMap:self.myTileMap];
Here's the wall image -
And here's the issue -
Case 1
(0,0) should be behind (0,1) according to calculateVertexZ method. And hence the sprite on ( (0,1) is rendered OVER sprite on (0,0).
Case 2
(4,0) should be behind (4,1) according to calculateVertexZ method. But somehow, because I'm adding block on (4,0) AFTER (4,1) its not giving me the desired results.
I had read that when two sprites have same vertexZ ONLY then whichever sprite was added later will be on top. But here the sprites have different vertexZ values, still order of creation is overriding that.
Also, I can't figure out what to do with zorder in this equation. SOMEBODY PLS HELP

I solved this by using the vertexZ property and zOrder property of the base tile.
-(void)addTile:(NSString *)tileName AtPos:(CGPoint)tilePos onTileMap:(CCTMXTiledMap *)tileMap
{
CCTMXLayer *floorLayer=[tileMap layerNamed:#"FloorLayer"];
NSAssert(floorLayer !=nil, #"Ground layer not found!");
CCSprite *baseTile = [floorLayer tileAt:tilePos];
CCSprite *addedTile = [[CCSprite alloc] initWithFile:tileName];
addedTile.anchorPoint = CGPointMake(0, 0);
addedTile.position = baseTile.position;
addedTile.vertexZ = baseTile.vertexZ;
[tileMap addChild:addedTile z:baseTile.zOrder tag:tile.tag];
}

Related

Scenekit - convertTransform of a rotated sub-subNode based on world axes

I have a following scene:
[Root Node]
|
[Main Container]
| |
[Node A Wrapper] [Node B Wrapper]
| |
[Node A] [Node B]
I've set up pan gesture recognizers in a way that when u pan in open space, the [Main Container] rotates in the selected direction by +/- Double.pi/2 (90deg). When the pan starts on one of the subnodes A, B (i'm hittesting for this on touchesBegan), i want to rotate the subnode along the direction of world axis (again 90deg increments).
I'm rotating the [Main Container] using convertTransform() from rootNode, which works fine, and the rotations are performed along the world axes - the position of main container is (0,0,0) which i believe makes it lot easier.
The reason why i wrapped the subnodes is so they have local positions (0,0,0) inside the wrapper, which should help with the rotation around their origin. But as they are rotated also when i perform rotate on [Main Container] , the direction of their local axes is changed and the rotation is performed around different axis than what i want.
In my (very limited) understanding of transformation matrices, i assume i need to somehow chain and multiply the matrices produced by convertTransform of the parent nodes, or to use the worldTransform property somehow, but anything i tried results in weird rotations. Any help would be appreciated!
I've set up a small sample project based on the SceneKit template, with controls similar as what you described. It's in Objective C but the relevant parts are pretty much the same:
- (void) handlePan:(UIPanGestureRecognizer*)gestureRecognize {
CGPoint delta = [gestureRecognize translationInView:(SCNView *)self.view];
if (gestureRecognize.state == UIGestureRecognizerStateChanged) {
panHorizontal = NO;
if (fabs(delta.x) > fabs(delta.y)) {
panHorizontal = YES;
}
} else if (gestureRecognize.state == UIGestureRecognizerStateEnded) {
SCNMatrix4 rotMat;
int direction = 0;
if (panHorizontal) {
if (delta.x <0) {
direction = -1;
} else if (delta.x >1) {
direction = 1;
}
rotMat= SCNMatrix4Rotate(SCNMatrix4Identity, M_PI_2, 0, direction, 0);
} else {
if (delta.y <0) {
direction = -1;
} else if (delta.y >1) {
direction = 1;
}
rotMat= SCNMatrix4Rotate(SCNMatrix4Identity, M_PI_2, direction, 0, 0);
}
if (selectedNode == mainPlanet) {
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat);
} else { //_selectedNode is a child node of mainPlanet, i.e. moons.
//get the translation matrix of the child node
SCNMatrix4 transMat = SCNMatrix4MakeTranslation(selectedNode.position.x, selectedNode.position.y, selectedNode.position.z);
//move the child node the origin of its parent (but keep its local rotation)
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(transMat));
//apply the "rotation" of the mainPlanet extra (we can use the transform because mainPlanet is at world origin)
selectedNode.transform = SCNMatrix4Mult( selectedNode.transform, mainPlanet.transform);
//perform the rotation based on the pan gesture
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat);
//remove the extra "rotation" of the mainPlanet (we can use the transform because mainPlanet is at world origin)
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform,SCNMatrix4Invert(mainPlanet.transform));
//add back the translation mat
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform,transMat);
}
}
}
In handleTap:
selectedNode = result.node;
In viewDidLoad:
mainPlanet = [scene.rootNode childNodeWithName:#"MainPlanet" recursively:YES];
orangeMoon = [scene.rootNode childNodeWithName:#"orangeMoon" recursively:YES];
yellowMoon = [scene.rootNode childNodeWithName:#"yellowMoon" recursively:YES];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[gestureRecognizers addObject:panGesture];
And local vars:
SCNNode *mainPlanet;
SCNNode *orangeMoon;
SCNNode *yellowMoon;
SCNNode *selectedNode;
BOOL panHorizontal;
MainPlanet would be your mainContainer and doesn't have to be visible (it does in my example because it has to be tapped to know what to rotate...). The two moons are your node A and B, child nodes of the main node. No wrappers necessary. The key part is obviously the commented portion.
Normally to rotate a child node in local space (IF the parent node is at 0,0,0)
First move it back to the node by multiplying its transform with the inverse of its translation only.
Apply the rotation matrix.
Apply the original translation we removed in step 1.
As you noticed that will rotate the child node on its local pivot point and over its local axis. This works fine until you rotate the parent node. The solution is to apply that same rotation to the child node before rotating it based on the pan gesture (step 2), and then after that remove it again.
So to get the results you desire:
First move it back to the node by multiplying its transform with the inverse of its translation only.
Apply the rotation of the parent node (since it's at 0,0,0 and I assume not scaled, we can use the transform).
Apply the rotation matrix based on the pan gesture.
Remove the rotation of the parent node
Apply the original translation we removed in step 1.
I’m sure there are other possible routes and perhaps instead of step 2 and 4 the rotation matrix could be converted to the main node using convert to/from but this way you can clearly tell what’s going on.

Check if node is visible on the screen

I currently have a large map that goes off the screen, because of this its coordinate system is very different from my other nodes. This has led me to a problem, because I'm needing to generate a random CGPoint within the bounds of this map, and then if that point is frame/on-screen I place a visible node there. However the check on wether or not the node is on screen continuously fails.
I'm checking if the node is in frame with the following code: CGRectContainsPoint(self.frame, values) (With values being the random CGPoint I generated). Now this is where my problem comes in, the coordinate system of the frame is completely different from the coordinate system of the map.
For example, in the picture below the ball with the arrows pointing to it is at coordinates (479, 402) in the scene's coordinates, but they are actually at (9691, 9753) in the map's coordinates.
I determined the coordinates using the touchesBegan event for those who are wondering. So basically, how do I convert that map coordinate system to one that will work for the frame?
Because as seen below, the dot is obviously in the frame however the CGRectContainsPoint always fails. I've tried doing scene.convertPoint(position, fromNode: map) but it didn't work.
Edit: (to clarify some things)
My view hierarchy looks something like this:
The map node goes off screen and is about 10,000x10,000 for size. (I have it as a scrolling type map). The origin (Or 0,0) for this node is in the bottom left corner, where the map starts, meaning the origin is offscreen. In the picture above, I'm near the top right part of the map. I'm generating a random CGPoint with the following code (Passing it the maps frame) as an extension to CGPoint:
static func randPoint(within: CGRect) -> CGPoint {
var point = within.origin
point.x += CGFloat(arc4random() % UInt32(within.size.width))
point.y += CGFloat(arc4random() % UInt32(within.size.height))
return point;
}
I then have the following code (Called in didMoveToView, note that I'm applying this to nodes I'm generating - I just left that code out). Where values is the random position.
let values = CGPoint.randPoint(map.totalFrame)
if !CGRectContainsPoint(self.frame, convertPointToView(scene!.convertPoint(values, fromNode: map))) {
color = UIColor.clearColor()
}
To make nodes that are off screen be invisible. (Since the user can scroll the map background). This always passes as true, making all nodes invisible, even though nodes are indeed within the frame (As seen in the picture above, where I commented out the clear color code).
If I understand your question correctly, you have an SKScene that contains an SKSpriteNode that is larger than the scene's view, and that you are randomly generating coordinates within that sprite's coordinate system that you want to map to the view.
You're on the right track with SKNode's convertPoint(_:fromNode:) (where your scene is the SKNode and your map is the fromNode). That should get you from the generated map coordinate to the scene coordinate. Next, convert that coordinate to the view's coordinate system using your scene's convertPointToView(_:). The point will be out of bounds if it is not in view.
Using a worldNode which includes a playerNode and having the camera center on this node, you can check on/off with this code:
float left = player.position.x - 700;
float right = player.position.x + 700;
float up = player.position.y + 450;
float down = player.position.y - 450;
if((object.position.x > left) && (object.position.x < right) && (object.position.y > down) && (object.position.y < up)) {
if((object.parent == nil) && (object.dead == false)) {
[worldNode addChild:object];
}
} else {
if(object.parent != nil) {
[object removeFromParent];
}
}
The numbers I used above are static. You can also make them dynamic:
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
Diving the screenWidth by 2 for left and right. Same for screenHeight.

SpriteKit Node Rotational Issues

I am trying to develop a basic game for iOS involving a rag doll-like entity. Basically (as you can tell below), I have a head, a body, and a right arm (all three are basic sprite nodes) connected via pin joints, simulating joins in the human body (roughly).
The head and the body work perfectly in that, when a force is applied, the body rotates around the head perfectly and eventually comes to a rest under the head, vertically (see picture).
The arm's base is pinned with a pin joint to the body and is supposed to rotate around its base (kind of like a shoulder) and it is set with an initial rotation of 45 degrees so it looks like an arm before the physics engine takes over.
My question is: why doesn't the arm come to rest in a vertical position (like the body) due to gravity? Shouldn't gravity cause the arm to rotate about its base until the tip of the arm rests directly below the top of the arm (shoulder)? Furthermore, when a force is applied to the body (shown in the example code below), the body rotates about the neck joint, exactly as it should, but the arm does not move from its current orientation (and this is not desirable).
If this is not the case, how would I achieve this effect?
Thank you for your time and I'd be happy to provide any additional information if desired
Picture of physics simulation at rest:
Relevant code which demonstrates the problem:
//make the head node
SKSpriteNode *head = [SKSpriteNode spriteNodeWithImageNamed:#"head"];
head.size = CGSizeMake(20 * [CFLConstants universalWidthScaleFactor], 20 * [CFLConstants universalWidthScaleFactor]);
head.position = position;
head.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:head.size.width/2];
head.physicsBody.categoryBitMask = CFLPhysicsCategoriesHead;
head.physicsBody.collisionBitMask = 0;
head.physicsBody.dynamic = NO;
[self.ragdollLayer addChild:head];
//make the body node
SKSpriteNode *body = [SKSpriteNode spriteNodeWithImageNamed:#"body"];
body.size = CGSizeMake(head.size.width, head.size.width * 3);
body.position = CGPointMake(head.position.x, head.position.y - head.size.height/2 - body.size.height/2);
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.categoryBitMask = CFLPhysicsCategoriesBody;
body.physicsBody.collisionBitMask = 0;
[self.ragdollLayer addChild:body];
//attach the head and the body (via a neck joint)
SKPhysicsJointPin *neckJoint = [SKPhysicsJointPin jointWithBodyA:head.physicsBody bodyB:body.physicsBody anchor:CGPointMake(head.position.x, head.position.y - head.size.height/2)];
[self.physicsWorld addJoint:neckJoint];
//make the right arm
SKSpriteNode *rightArm = [SKSpriteNode spriteNodeWithImageNamed:#"arm"];
rightArm.size = CGSizeMake(head.size.width/5, head.size.width/5 * 10);
rightArm.anchorPoint = CGPointZero;
CGPoint rightArmPosition = CGPointMake(body.position.x + body.size.width * 1/5, body.position.y + body.size.height * 1/5);
rightArm.position = rightArmPosition;
rightArm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rightArm.size];
rightArm.physicsBody.categoryBitMask = CFLPhysicsCategoriesRightArm;
rightArm.physicsBody.collisionBitMask = 0;
rightArm.zRotation = -M_PI_4;
//force which makes the arm problem even more noticeable
[body.physicsBody applyImpulse:CGVectorMake(100, 0)];
[self.ragdollLayer addChild:rightArm];
//make the joint which holds the right arm to the body, but should allow the arm to rotate about this point (and doesn't)
SKPhysicsJointPin *rightShoulderJoint = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:rightArm.physicsBody anchor:rightArmPosition];
[self.physicsWorld addJoint:rightShoulderJoint];
In my experience, this is because changing the anchor point on the sprite, doesn't change the anchor point for the physics body. Though I swear sometimes you don't have to, so maybe it's an order of operations thing. But anyways, offset the center of the physics body to account for the sprite anchor point. Something like:
spriteRightArm.anchorPoint = CGPointMake(0, 1);
spriteRightArm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteRightArm.size center:CGPointMake(spriteRightArm.size.width/2, -spriteRightArm.size.height/2)];

Post Path Projection ( trail ) in cocos2d iOS

i want to create path projection like angry birds.
When i throw my ball i want to show projection on ball that on this path my ball has thrown.
I have seen a post on stackoverflow and i have implemented it .
if(trailtimer % 2 == 0 && isFired)
{
CCSprite * dot_Sprite = [[CCSprite alloc] initWithFile:#"whitedot.png"];
dot_Sprite.position = ccp(b->GetPosition().x * PTM_RATIO,b->GetPosition().y * PTM_RATIO);
dot_Sprite.scale = 0.1;
[self addChild:dot_Sprite z:2 tag:111];
}
}
In this code its working fine . but adding multiple sprites will slow my game.
Is there anyother option/way to implement path projection in my game.
You could try to create the dot like this:
CCSprite * dot_Sprite = [[CCSprite spriteWithFile:#"whitedot.png"];
and see if it helps.
This will add the dot to the auto release pool and make sure memory is free after usage

Fancy Effects on MKMapOverlay CGPath

I'm using a MKOverlayView for drawing a path onto the apple maps. I'd like to draw many short paths onto it, because I need to colorize the track depending on some other values. But I'm getting some fancy effects doing it that way ... also my start- and ending points are connected, but I don't know why. After zooming in/out the fancy-effect-pattern changes and gets bigger/smaller. It seems that you can see the apple map tiles on my path ...
This is my code, its called inside the drawMapRect method of my overlay view.
for(int i = 0; i < tdpoints.pointCount-1; i++ ){
CGPoint firstCGPoint = [self pointForMapPoint:tdpoints.points[i]];
CGPoint secCGPoint = [self pointForMapPoint:tdpoints.points[i+1]];
if (lineIntersectsRect(tdpoints.points[i], tdpoints.points[i+1], clipRect)){
double val1 = (arc4random() % 10) / 10.0f;
double val2 = (arc4random() % 10) / 10.0f;
double val3 = (arc4random() % 10) / 10.0f;
CGContextSetRGBStrokeColor(context, val1 ,val2, val3, 1.0f);
CGContextSetLineWidth(context, lineWidth);
CGContextBeginPath(context);
CGContextMoveToPoint(context,firstCGPoint.x,firstCGPoint.y);
CGContextAddLineToPoint(context, secCGPoint.x, secCGPoint.y);
CGContextStrokePath(context);
CGContextClosePath(context);
}
}
http://imageshack.us/photo/my-images/560/iossimulatorbildschirmf.jpg/
http://imageshack.us/photo/my-images/819/iossimulatorbildschirmf.jpg/
I'm adding my GPS Points like that. (From Breadcrumbs Apple Example)
CLLocationCoordinate2D coord = {.latitude = 49.1,.longitude =12.1f};
[self drawPathWithLocations:coord];
CLLocationCoordinate2D coord1 = {.latitude = 49.2,.longitude =12.2f};
[self drawPathWithLocations:coord1];
CLLocationCoordinate2D coord2 = {.latitude = 50.1,.longitude =12.9f};
[self drawPathWithLocations:coord2];
This is the adding Method:
-(void) drawPathWithLocations:(CLLocationCoordinate2D)coord{
if (!self.crumbs)
{
// This is the first time we're getting a location update, so create
// the CrumbPath and add it to the map.
//
_crumbs = [[CrumbPath alloc] initWithCenterCoordinate:coord];
[self.trackDriveMapView addOverlay:self.crumbs];
// On the first location update only, zoom map to user location
[_trackDriveMapView setCenterCoordinate:coord zoomLevel:_zoomLevel animated:NO];
} else
{
// This is a subsequent location update.
// If the crumbs MKOverlay model object determines that the current location has moved
// far enough from the previous location, use the returned updateRect to redraw just
// the changed area.
//
// note: iPhone 3G will locate you using the triangulation of the cell towers.
// so you may experience spikes in location data (in small time intervals)
// due to 3G tower triangulation.
//
MKMapRect updateRect = [self.crumbs addCoordinate:coord];
if (!MKMapRectIsNull(updateRect))
{
// There is a non null update rect.
// Compute the currently visible map zoom scale
MKZoomScale currentZoomScale = (CGFloat)(self.trackDriveMapView.bounds.size.width / self.trackDriveMapView.visibleMapRect.size.width);
// Find out the line width at this zoom scale and outset the updateRect by that amount
CGFloat lineWidth = MKRoadWidthAtZoomScale(currentZoomScale);
updateRect = MKMapRectInset(updateRect, -lineWidth, -lineWidth);
// Ask the overlay view to update just the changed area.
[self.crumbView setNeedsDisplayInMapRect:updateRect];
}
}
This is the addCoordinate method:
- (MKMapRect)addCoordinate:(CLLocationCoordinate2D)coord
{
pthread_rwlock_wrlock(&rwLock);
// Convert a CLLocationCoordinate2D to an MKMapPoint
MKMapPoint newPoint = MKMapPointForCoordinate(coord);
MKMapPoint prevPoint = points[pointCount - 1];
// Get the distance between this new point and the previous point.
CLLocationDistance metersApart = MKMetersBetweenMapPoints(newPoint, prevPoint);
NSLog(#"PUNKTE SIND %f METER AUSEINANDER ... ", metersApart);
MKMapRect updateRect = MKMapRectNull;
if (metersApart > MINIMUM_DELTA_METERS)
{
// Grow the points array if necessary
if (pointSpace == pointCount)
{
pointSpace *= 2;
points = realloc(points, sizeof(MKMapPoint) * pointSpace);
}
// Add the new point to the points array
points[pointCount] = newPoint;
pointCount++;
// Compute MKMapRect bounding prevPoint and newPoint
double minX = MIN(newPoint.x, prevPoint.x);
double minY = MIN(newPoint.y, prevPoint.y);
double maxX = MAX(newPoint.x, prevPoint.x);
double maxY = MAX(newPoint.y, prevPoint.y);
updateRect = MKMapRectMake(minX, minY, maxX - minX, maxY - minY);
}
pthread_rwlock_unlock(&rwLock);
return updateRect;
}
Hint
I think my refresh algorithm only refreshes one tile of the whole map on the screen and because every time the drawMapRect method is called for this specific area a new random color is generated. (The rest of the path is clipped and the oder color remains ...).
The "fancy effects" you see are a combination of the way MKMapView calls drawMapRect and your decision to use random colours every time it is draw. To speed up display when the user pans the map around MKMapView caches tiles from your overlay. If one tile goes off screen it can be thrown away or stored in a different cache or something, but the ones still on screen are just moved about and don't need to be redrawn which is good because drawing might mean a trip to your data supply or some other long calculation. That's why you call setNeedsDisplayInMapRect, it only needs to fetch those tiles and not redraw everything.
This works in all the apps I've seen and is a good system on the whole. Except for when you draw something that isn't going to be the same each time, like your random colours. If you really want to colour the path like that then you should use a hash or something that seems random but is really based on something repeatable. Maybe the index the point is at, multiplied by the point coordinate, MD5ed and then take the 5th character and etc etc. What ever it is it must generate the same colour for the same line no matter how many times it is called. Personally I'd rather the line was one colour, maybe dashed. But that's between you and your users.
because whenever you draw any path you need to close it. and as you close the path it automatically draws line between lastPoint and firstPoint.
just remove last line in your path drawing
CGContextClosePath(context);
The purpose of CGContextClosePath is to literally close the path - connect start and end points. You don't need that, StrokePath drew the path already. Remove the line. Also move CGContextStrokePath outside your loop, the approach is to move/add line/move/add line... and then stroke (you can change colors as you do this, which you are).
For the "fancy effects" (tilted line joining), investigate the effects of possible CGContextSetLineJoin and CGContextSetLineCap call parameters.

Resources