What's the best way to do movement in a 2D square grid system? I have this something that works but it seems wrong/ugly (see below).
x x x x x x x
x x x x x x x
x x x O x x x
x x x U x x x
x x x x x x x
x x x x x x x
x x x x x x x
For example, U is the unit I want to move, and O is an impassable object like another unit or a mountain. If U can move 3 tiles, I want the moveable area (M) to look like this.
x x x x x x x
x x M x M x x
x M M O M M x
M M M U M M M
x x M M M M x
x x M M M x x
x x x M x x x
Here's my code:
public function possibleMoves(range:uint, cords:Array):void {
var X:uint = cords[0];
var Y:uint = cords[1];
if (range > 0) {
try {
theGrid[X + 1][Y].moveable = true;
if (theGrid[X + 1][Y].getOccupied == false) {
possibleMoves(range - 1, [X + 1, Y], flag, mtype);
}
} catch (err:Error) { }
try {
theGrid[X - 1][Y].moveable = true;
if (theGrid[X - 1][Y].getOccupied == false) {
possibleMoves(range - 1, [X - 1, Y], flag, mtype);
}
} catch (err:Error) { }
try {
theGrid[X][Y + 1].moveable = true;
if (theGrid[X][Y + 1].getOccupied == false) {
possibleMoves(range - 1, [X, Y + 1], flag, mtype);
}
} catch (err:Error) { }
try {
theGrid[X][Y - 1].moveable = true;
if (theGrid[X][Y - 1].getOccupied == false) {
possibleMoves(range - 1, [X, Y - 1], flag, mtype);
}
} catch (err:Error) { }
}
the data structure of your tileset seems strongly coupled to a "Tile" class that does too many things ; theGrid[X][Y].moveable, theGrid[X][Y].getOccupied... + probably some other methods.
maybe the tileset data structure should only store Boolean values (walkable?true/false) and have a single method to tell wether a tile is walkable or not. in this case, a Vector of Boolean values is enough. testing the 4 ( or 8 with diagonals ) naerby values is pretty fast and spreading the test to the newly found values can be done with a recursive loop.
if you have different types of tiles (walls, objects, characters etc.), you could use a Vector.< int > rather than Booleans ; 0 would be a walkable tile and anything else would be forbidden areas.
this allows a Boolean check : as 0 = false and any other value = true.
I've done a sample here http://wonderfl.net/c/bRV8 ; it might be clearer than pasting the code. move the mouse around, you should see a pinky shape the gives you the valid cells.
l.53 is the "connexity" possible valeus are 4 and 8
four connected gives
eight connected
l.54 is the max recursion depth
as such, the recursion is performed regardless of the starting point. it will spill in a sometimes unexpected way.
if you need to give a specific amount of moves this won't be enough, you'll have to set up some kind of pathfinder.
Edit:
It appears that the code provided works, but contains a recursion termination bug that is attempted to be avoided by the following line. This works only in some cases and behaves really weird if you put your character at the edge of the map or give him number of moves other than 5:
var max:int = ( maxDepth * maxDepth );
if( maxDepth % 2 == 0 )max--;
recursiveCheck( valid, tilesetClone, 0, max, connexity );
I checked with different recursion depth, and the bug quickly becomes apparent. Lack of grid and complex map design of this example obscures the bug, but here's a screenshot below - note that if mouse is positioned in the corner like shown, the field extends 6 squares up and 7 squares left, while it should've only been 5.
Your code will work, but is far from elegant. A lot of tiles will be calculated multiple times. You could fix this by caching the results for each gridTile.
Have a look at the Memoization technique.
Here's the correct solution to the recursion to the avoiding obstacles on a 2D tile map problem in objective-c. Took me good 4.5 hours translate action script to objective-c and debug it... almost 3AM now :) To use this, just create a map of X by Y squares, put your model on the map and call
-(NSMutableArray*)possibleMovesFromIndex:(int)tileIndex movesCount:(int)moves allowDiagonalMoves:(BOOL)allowDiagonal
The resulting array will give you locations that your character can reach with the given number of moves. You can then use A* pathfinding algorithm to animate movement from the current position to any one of highlighted tiles.
I have attempted to be super-verbose in my names and descriptions, as it was quite difficult to trace these points through all these method calls without it.
MapOfTiles.h:
#import <Foundation/Foundation.h>
#define tileCountWide 14
#define tileCountTall 8
#interface MapOfTiles : NSObject
#property (nonatomic,strong)NSMutableArray* tilesetWalkable;
#property (nonatomic)int width;
#property (nonatomic)int height;
#property (nonatomic,readonly)int tileCount;
-(id)initWithXWidth:(int)xWidth yHeight:(int)yHeight;
-(CGPoint)pointFromIndex:(int)index;
-(NSMutableArray*)possibleMovesFromIndex:(int)tileIndex movesCount:(int)moves allowDiagonalMoves:(BOOL)allowDiagonal;
#end
MapOfTiles.m
#import "MapOfTiles.h"
#implementation MapOfTiles
-(id)initWithXWidth:(int)xWidth yHeight:(int)yHeight
{
self = [super init];
if (self) {
self.width = xWidth;
self.height = yHeight;
int count = xWidth*yHeight;
self.tilesetWalkable = [[NSMutableArray alloc] initWithCapacity:count];
for(int i = 0 ; i<count; i++)
{
//initial map is blank and has no obstacles
[self.tilesetWalkable addObject:[NSNumber numberWithBool:YES]];
}
}
return self;
}
-(int)tileCount
{
return self.width*self.height;
}
-(NSMutableArray*)possibleMovesFromIndex:(int)tileIndex movesCount:(int)moves allowDiagonalMoves:(BOOL)allowDiagonal
{
int connexity = 4;
if(allowDiagonal)
{
connexity = 8;
}
//check if there is an obstacle at the origin
NSNumber* movementOrigin = self.tilesetWalkable[tileIndex];
//if the first tile is walkable, proceed with seeking recursive solutions using 4 or 8 connected tiles
if(movementOrigin.boolValue == YES)
{
//create a copy to avoid messing up the real map
NSMutableArray* tilesetClone = [NSMutableArray arrayWithArray:self.tilesetWalkable];
//will contain tileset indices where you can reach in the given number of moves if you can only move in a straight line or straight line and diagonally
NSMutableArray* validMoves = [NSMutableArray arrayWithCapacity:10];
//we start building our array of walkable tiles with the origin, because we just tested it
NSNumber* originIsWalkable = [NSNumber numberWithInt:tileIndex];
NSMutableArray* initialWalkableTilesArray = [NSMutableArray arrayWithObject:originIsWalkable];
//for the first recursion, we manually set the origin to be not walkable, so recursion cannot return to it
[tilesetClone replaceObjectAtIndex:tileIndex withObject:[NSNumber numberWithBool:NO]];
[validMoves addObject:initialWalkableTilesArray];
[self recursiveCheckWithValidMovesArray:validMoves
tileset:tilesetClone
currentMove:0
maxMoves:moves
connexity:connexity];
return validMoves;
}
return nil;
}
-(void)recursiveCheckWithValidMovesArray:(NSMutableArray*)validMovesToPopulate tileset:(NSMutableArray*)tileset currentMove:(int)currentDepth maxMoves:(int)maxDepth connexity:(int)connexity
{
if(currentDepth == maxDepth)
{
return;
}else
{
NSArray* movesToCheck = [validMovesToPopulate objectAtIndex:currentDepth];
DLog(#"checking moves: %#",movesToCheck);
for (NSNumber* walkableMapIndex in movesToCheck)
{
//check array for valid moves
NSMutableArray* validMovesFromPoint = [self getValidMovesFromPoint:[self pointFromIndex:walkableMapIndex.intValue]
lockMovesInTileset:tileset
usingConnexity:connexity];
//remember valid moves, so the next iteration will check them
if(validMovesToPopulate.count == currentDepth+1)
{
//this is the first time we are looking at moves at this depth, so add an array that will hold these moves
[validMovesToPopulate addObject:validMovesFromPoint];
}else
{
//there is already an array at this depth, just add more values to it
NSMutableArray* validTilesForThisMove = validMovesToPopulate[currentDepth+1];
[validTilesForThisMove addObjectsFromArray:validMovesFromPoint];
}
}
if(movesToCheck.count>0)
{
[self recursiveCheckWithValidMovesArray:validMovesToPopulate
tileset:tileset
currentMove:++currentDepth
maxMoves:maxDepth
connexity:connexity];
}else
{
return;
}
}
}
-(CGPoint)pointFromIndex:(int)index
{
//for a field that is 8 tall by 12 wide with 0,0 in bottom left
//tileCountTall is also number of rows
//x is column
int x = index / tileCountTall;
//y is row
int y = index % tileCountTall;
CGPoint xyPointInTileset = CGPointMake(x, y);
DLog(#"Examing index: %i assigned:x%.0f, y:%.0f",index, xyPointInTileset.x,xyPointInTileset.y);
return xyPointInTileset;
}
-(int)indexFromPoint:(CGPoint)point
{
return [self indexFromX:point.x y:point.y];
}
-(int)indexFromX:(int)x y:(int)y
{
//in my case the map is rectangular
if ( x < 0 ) x = 0;
int tileWidth = tileCountWide -2 ;//in my case, 2 rows of grid are hidden off screen for recycling of map segments
if ( x > tileWidth - 1 ) x = tileWidth - 1;
if ( y < 0 ) y = 0;
if ( y > tileCountTall - 1 ) y = tileCountTall - 1;
#warning this might screw up the algorithm, because for me x and y values are mapped differently?
return x * tileCountTall + y;
return 0;
}
-(void)lockTileAtIndex:(int)index forTileset:(NSMutableArray*)tileset rememberValidMovesInThisArray:(NSMutableArray*)tiles
{
DLog(#"Locking tile: %i",index);
//we lock this tile, so it is not checked by future recursions
NSNumber* tileIsNotWalkableAtIndex = [NSNumber numberWithBool:NO];
[tileset replaceObjectAtIndex:index withObject:tileIsNotWalkableAtIndex];
//remember that this index is a valid move
[tiles addObject:[NSNumber numberWithInt:index]];
}
-(NSMutableArray*)getValidMovesFromPoint:(CGPoint)p lockMovesInTileset:(NSMutableArray*)tileset usingConnexity:(int)connexity
{
int i = 0;
NSMutableArray* validMovesFromThisPoint = [NSMutableArray array];//these tiles are valid moves from point
NSNumber* tileIsWalkable = nil;
//using (x,y) (0,0) as bottom left corner, Y axis pointing up, X axis pointing right
i = [self indexFromPoint:CGPointMake(p.x-1, p.y)];//left
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES)
{
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x+1, p.y)];//right
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES)
{
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x, p.y-1)];//bottom
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES)
{
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x, p.y+1)];//top
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES)
{
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
if(connexity == 4){
return validMovesFromThisPoint;//if we want a connexity 4, no need to go further
}
i = [self indexFromPoint:CGPointMake(p.x-1, p.y-1)];//bottom left
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES){
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x+1, p.y-1)];//bottom right
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES){
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x-1, p.y+1)];//top left
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES){
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
i = [self indexFromPoint:CGPointMake(p.x+1, p.y+1)];///top right
tileIsWalkable = tileset[i];
if(tileIsWalkable.boolValue == YES){
[self lockTileAtIndex:i forTileset:tileset rememberValidMovesInThisArray:validMovesFromThisPoint];
};
return validMovesFromThisPoint;
}
#end
Related
Bullet objects are created with .l for location and .vel for velocity. I'm trying with a large radius of 30 and still they never collide.
- (void)bulletsFired:(NSArray *)bullets li:(int)li {
[self playBulletSound:li];
for (Bullet *b in bullets) {
[self addChild:b];
b.tag = -1;
b.shotNumber = shotsFired;
}
for (Bullet *b in bullets) {
for (Bullet *bb in self.bullets) {
float timeOfApproach = TimeOfClosestApproach(b.l, b.vel, bb.l, bb.vel, 30, 30);
if (timeOfApproach > 0) {
NSLog(#"time of : %f", timeOfApproach);
NSString *keyName = [self.collisions objectForKey:[self keyNameForTime:(int)timeOfApproach]];
NSMutableArray *timedCollisions = [self.collisions objectForKey:keyName];
if (timedCollisions == nil) {
NSMutableArray *newCollisions = [NSMutableArray array];
[self.collisions setObject:newCollisions forKey:keyName];
}
NSDictionary *collision = #{#"b1" : [NSString stringWithFormat:#"%d", bb.shotNumber], #"b2" : [NSString stringWithFormat:#"%d", b.shotNumber]};
[timedCollisions addObject:collision];
}
}
}
[self.bullets addObjectsFromArray:bullets];
[self.scoreCycler score:1];
}
I check for timeOfClosestApproach with this function:
float TimeOfClosestApproach(CGPoint Pa, CGPoint Pb, CGPoint Va, CGPoint Vb, float Ra, float Rb) {
CGPoint Pab = ccpSub(Pa, Pb);
CGPoint Vab = ccpSub(Va, Vb);
float a = ccpDot(Vab, Vab);
float b = 2 * ccpDot(Pab, Vab);
float c = ccpDot(Pab, Pab) - (Ra + Rb) * (Ra + Rb);
// The quadratic discriminant.
float discriminant = b * b - 4 * a * c;
// Case 1:
// If the discriminant is negative, then there are no real roots, so there is no collision. The time of
// closest approach is then given by the average of the imaginary roots, which is: t = -b / 2a
float t;
if (discriminant < 0) {
t = -b / (2 * a);
return -1;
} else {
// Case 2 and 3:
// If the discriminant is zero, then there is exactly one real root, meaning that the circles just grazed each other. If the
// discriminant is positive, then there are two real roots, meaning that the circles penetrate each other. In that case, the
// smallest of the two roots is the initial time of impact. We handle these two cases identically.
float t0 = (-b + (float)sqrt(discriminant)) / (2 * a);
float t1 = (-b - (float)sqrt(discriminant)) / (2 * a);
t = min(t0, t1);
// We also have to check if the time to impact is negative. If it is negative, then that means that the collision
// occured in the past. Since we're only concerned about future events, we say that no collision occurs if t < 0.
if (t < 0) {
return -1;
} else {
}
}
// Finally, if the time is negative, then set it to zero, because, again, we want this function to respond only to future events.
if (t < 0) {
t = 0;
}
return t;
}
I keep getting -1 returned, and the bullets are never predicted to collide, even when they should based on my eyesight.
if (t < 0) {
return -1;
} else {
}
Keeps getting triggered.
What's wrong with my timeOfClosestApproach function?
So I need help figuring out what code I am missing here. I have checked all over the place, but I need specifics on wether its the formulas used or a typo that i haven't noticed yet.
Here is the polygon class. I am trying to create random polygons with 8 vertices and then of course fill with a plain color. But I want them to continue to generate random position but leave them fixed. In a better way the poly's are my terrain.Ok revise: the polygons are there and my character interacts with them, but I cannot see them, and yes they are on the same layer. Oh but they don't keep generating at the bottom, which i am guessing i just need to delete the old ones once they go off the screen and it should make a new poly.
-(void) genBody:(b2World *)world pos:(CGPoint *)pos {
//Here we generate a somewhat random convex polygon by sampling
//the "eccentric anomaly" of an ellipse with randomly generated
//x and y scaling constants (a,b). The algorithm is limited by
//the parameter max_verts, and has a number of tunable minimal
//and scaling values.
// I need to change this to randomly choosing teh number of vertices between 3-8,
// then choosing random offsets from equally distributed t values.
// This will eliminate teh while loop.
screen_pos = ccp(pos->x, pos->y);
float cur_t;
float new_t;
float delta_t;
float min_delta_t = 0.5;
float t_scale = 1.5;
b2Vec2 *verts= new b2Vec2[m_maxVerts]; // this should be replaced by a private verts ... maybe ... hmm that will consume more ram though
float t_vec[m_maxVerts];
// Generate random vertices
int vec_len;
while (true) {
cur_t = 0.0;
for (vec_len=0; vec_len<m_maxVerts; vec_len++) {
//delta_t = t_scale*(float)rand()/(float)RAND_MAX; // wish they just had a randf method :/
delta_t = t_scale*floorf((double)arc4random()/ARC4RANDOM_MAX);
#ifdef POLY_DEBUG
CCLOG(#"delta_t %0.2f", delta_t);
#endif
if (delta_t < min_delta_t) {
delta_t = min_delta_t;
}
new_t = cur_t + delta_t;
if (new_t > 2*PI) {
break;
}
t_vec[vec_len] = new_t;
cur_t = new_t;
}
// We need at least three points for a triangle
if ( vec_len > 3 ) {
break;
}
}
At least where the body is being generated.
then...
float num_verts = vec_len;
b2BodyDef BodyDef;
BodyDef.type = b2_staticBody;
BodyDef.position.Set(pos->x/PTM_RATIO, pos->y/PTM_RATIO);
BodyDef.userData = self; // hope this is correct
m_polyBody = world->CreateBody(&BodyDef);
b2PolygonShape polyShape;
int32 polyVert = num_verts;
polyShape.Set(verts, polyVert);
b2FixtureDef FixtureDef;
FixtureDef.shape = &polyShape;
FixtureDef.userData = self; // hope this is correct
FixtureDef.density = 1.6f;
FixtureDef.friction = 0.4f;
FixtureDef.restitution = 0.5f;
m_polyBody->CreateFixture(&FixtureDef);
for (int i=0; i < num_verts; i++) {
// Convert from b2Vec2 to CCPoint and from physics units to pixels
m_verts[i] = ccp(verts[i].x*PTM_RATIO, verts[i].y*PTM_RATIO);
}
m_numVerts = num_verts;
delete verts;
}
-(void) setColor:(ccColor4F) color {
m_color = color;
}
-(BOOL) dirty {
return true;
}
-(void) draw {
//[super draw];
ccDrawPoly(m_verts, m_numVerts, YES);
CCLOG(#"Drawing?");
}
-(CGAffineTransform) nodeToParentTransform {
b2Vec2 pos = m_polyBody->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
/*if ( ignoreAnchorPointForPosition_ ) {
x += anchorPointInPixels_.x;
y += anchorPointInPixels_.y;
}*/
// Make matrix
float radians = m_polyBody->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if( ! CGPointEqualToPoint(anchorPointInPixels_, CGPointZero) ){
x += c*-anchorPointInPixels_.x + -s*-anchorPointInPixels_.y;
y += s*-anchorPointInPixels_.x + c*-anchorPointInPixels_.y;
}
// Rot, Translate Matrix
transform_ = CGAffineTransformMake( c, s,
-s, c,
x, y );
return transform_;
}
there is some stuff in between but its less important. I can post it if asked.
Then the update function, which is based in my game scene class.
-(void)updateObstacles
{
//CCLOG(#"updating obstacles");
int xpos;
int ypos;
CGPoint pos;
for (int i=0; i<MAX_OBSTACLES; i++ ) {
// If there is no obstacle generate a new one
if ( obstacles[i] == NULL ) {
polyObstacleSprite *sprite = [[polyObstacleSprite alloc] init];
ypos = int(_winSize.width/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.width/2;
xpos = int(_winSize.height/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.height/2;
//CCLOG(#"generating obstacle at %d,%d", xpos, ypos);
pos = ccp(xpos, ypos);
[sprite genBody:_world pos:&pos];
[self addChild:sprite z:1];
obstacles[i] = sprite;
}
//CCLOG(#"position: %d, %d", obstacles[i]->screen, obstacles[i]->position.y); FINISH
}
}
Sorry if its sort of a mess I set this up quick, but pretty much what I want to do is have randomly generated polygons appear at the bottom of my iphone screen as my character moves down with gravity. I got everything else but the polygons working.
Thanks in advance for spending the time to read this.
I have a UIView and I want to add a series of programmatically generated UIButtons to it.
I've created a method that places each button (of standard size) via a CGPoint.
I want to lay these buttons out 3 in a row, over as many rows as needed.
I've no idea how to go about programatically generating the x and y coordinates to place the items on the view. I'm stuck on how to get the buttons to drop onto a new row once three have been placed.
Here's my code, I want to process something in this method to return the coordinates based only upon the index of the button:
- (CGPoint)calculateCoordinatesWithIndex:(NSInteger)index{
NSInteger x = 100;
NSInteger y = 50;
return CGPointMake(x, y);
}
The only solutions I can think of seem really clunky and inefficient.
Hello what about something like :
#define BUTTON_WIDTH 100
#define BUTTON_HEIGHT 50
- (void) placeButtonsFromButtonsArray:(NSArray *) buttons{
CGFloat currentXPosition = 0.0;
CGFloat currentYPosition = 0.0;
for (NSUInteger i = 0; i < [buttons count]; i++){
b.frame = CGRectMake(currentXPosition, currentYPosition, BUTTON_WIDTH, BUTTON_HEIGHT);
if (i%3 == 2){
currentXPosition = 0;
currentYPostion += BUTTON_HEIGHT;
}
else currentXPosition += BUTTON_WIDTH;
}
}
or Using your method :
- (CGPoint)calculateCoordinatesWithIndex:(NSInteger)index{
//computes on which line the button should be
NSUInteger line = (index/3);
//computes on which row the button should be
NSUInteger row = (index%3) ;
return CGPointMake(row*BUTTON_WIDTH, line*BUTTON_HEIGHT);
}
If you're targeting iOS 6, you could use a UICollectionView for this.
try this,
in .h
{
NSInteger x;
NSInteger y;
int noOfbtninrow;
}
-(void)viewDidload
{
x = 100;
y = 50;
noOfbtninrow=0;
}
- (CGPoint)calculateCoordinatesWithIndex:(NSInteger)index
{
if(noOfbtninrow==3)
{
x=100;
y=y+100
noOfbtninrow=0;
}
else
{
noOfbtninrow++;
x=x+100;
}
return CGPointMake(x, y);
}
[Edit: Rectangle definition added at bottom.]
[Edit2: XYPoint interface added at bottom.]
I'm working on a method that checks if two rectangles overlap. (Yeah, I'm in Kochan's Programming in Objective-C, doing the exercises, and I am painfully new at this.) When I compile this, the error message is: "Invalid operands to binary +". I get it on the first if statement and on the if-else that follows it.
I think I have an issue with pointers, but Kochan doesn't talk about this much.
And, if I take out these lines, the rest of the method works just fine. And the relevant variables are all floating type.
Help?
Also, any other thoughts on the method would be totally welcome. (Like, how do I make lines of code not go out so long. Like I said, painfully new at this.)
-(void) overlap: (Rectangle *)r2
{
overlapRectangle = [[Rectangle alloc] init];
leftRectangle = [[Rectangle alloc] init];
rightRectangle = [[Rectangle alloc] init];
lowerRectangle = [[Rectangle alloc] init];
upperRectangle = [[Rectangle alloc] init];
BOOL xIntersect = NO;
BOOL yIntersect = NO;
// Test to see if the Rectangle contains, or is equal to, Rectangle b
if (origin.x <= r2.origin.x && origin.y <= r2.origin.y && (origin.x + width) >= (r2.origin + r2.width) && (origin.y + height) >= (r2.origin.y + r2.height) )
{
overlapRectangle = r2;
}
// Test to see if Retangle b contains, or is equal to, the Rectangle
else if (origin.x >= r2.origin.x && origin.y >= r2.origin.y && origin.x + width <= r2.origin + r2.width && origin.y + height <= r2.origin.y + r2.height )
{
overlapRectangle = self;
}
// I should add tests for triangles overlapping on three
// sides or overlapping on two sides, but I'm not going
// to right now. Just learning objects and methods.
// Test to see if rectangles overlap on the x-axis
// Current is an if, because I wanted to run the code below
// to see if it worked, and it did.
if (origin.x <= r2.origin.x)
{
leftRectangle = self;
rightRectangle = r2;
}
else
{
rightRectangle = self;
leftRectangle = r2;
}
if (rightRectangle.origin.x + rightRectangle.width > leftRectangle.origin.x)
{
xIntersect = YES;
}
// Test to see if rectangles overlap on the y-axis
if (origin.y <= r2.origin.y)
{
lowerRectangle = self;
upperRectangle = r2;
}
else
{
lowerRectangle = self;
upperRectangle = r2;
}
if (lowerRectangle.origin.y + lowerRectangle.height > upperRectangle.origin.y)
{
yIntersect = YES;
}
// If retangles overlap on both the x-axis and y-axis,
// determination of overlapping rectangle's origin, height, and width
// and display same.
if (xIntersect == YES && yIntersect == YES)
{
overlapRectangle.origin.y = upperRectangle.origin.y;
overlapRectangle.origin.x = rightRectangle.origin.x;
overlapRectangle.height = lowerRectangle.height - (upperRectangle.origin.y - lowerRectangle.origin.y);
overlapRectangle.width = leftRectangle.width - (rightRectangle.origin.x - leftRectangle.origin.x);
NSLog (#"Your rectangles overlap.");
NSLog (#"Rectangle: w = %g, h = %g", overlapRectangle.width, overlapRectangle.height);
NSLog (#"Area = %g, Perimeter = %g", [overlapRectangle area], [overlapRectangle perimeter]);
NSLog (#"Origin at (%g, %g)", overlapRectangle.origin.x, overlapRectangle.origin.y);
}
else
{
NSLog (#"Your rectangles do not overlap.");
}
[overlapRectangle autorelease];
[leftRectangle autorelease];
[rightRectangle autorelease];
[lowerRectangle autorelease];
[upperRectangle autorelease];
}
Rectangle Definition:
// Interface, Rectangle Class
#interface Rectangle : NSObject
{
float width;
float height;
XYPoint *origin;
// For overlapping calculations
Rectangle *overlapRectangle;
Rectangle *leftRectangle;
Rectangle *rightRectangle;
Rectangle *lowerRectangle;
Rectangle *upperRectangle;
}
#property float width, height;
-(XYPoint *) origin;
-(void) setOrigin: (XYPoint *) pt;
-(void) setWidth: (float) w andHeight: (float) h;
-(float) area;
-(float) perimeter;
-(void) print;
-(void) translate;
-(void) overlap: (Rectangle *)r2;
-(void) draw;
#end
XYPoint interface:
#import <Foundation/Foundation.h>
#interface XYPoint : NSObject
{
float x;
float y;
}
#property float x, y;
-(void) setX: (float) xVal andY: (float) yVal;
#end
You've just got what is probably a typo:
// Test to see if the Rectangle contains, or is equal to,
// Rectangle b
if (origin.x <= r2.origin.x && origin.y <= r2.origin.y &&
(origin.x + width) >= (r2.origin + r2.width) &&
//^^^This is trying to add an XYPoint,
// which is an object, to a float.
(origin.y + height) >= (r2.origin.y + r2.height) )
{
overlapRectangle = r2;
}
// Test to see if Rectangle b contains, or is equal to,
// the Rectangle
else if (origin.x >= r2.origin.x && origin.y >= r2.origin.y &&
origin.x + width <= r2.origin + r2.width &&
//^^^Same thing.
origin.y + height <= r2.origin.y + r2.height )
{
...
The compiler should have told you what the types were that you were asking to be added:
error: invalid operands to binary + (have 'struct XYPoint *' and 'float')
that's the key. You just need to change the r2.origin to r2.origin.x so that you're adding two floats.
As for the length of the lines, there's two things you can do. You can move each segment of the conditions to different lines as I've done, but it would probably be best to create a couple of methods for Rectangle that will do the tests for you. This will make the code more readable, so when you come back to it in six months and the line reads:
if( [self containsRectangle:r2] || [self isEqualToRectangle:r2] ){
you'll know what's going on right away. Here's some suggestions for that:
- (BOOL)containsRectangle:(Rectangle *)otherRect {
BOOL originBelow = ((origin.x <= otherRect.origin.x) &&
(origin.y <= otherRect.origin.y));
float maxX = origin.x + width;
float otherMaxX = otherRect.origin.x + otherRect.width;
BOOL maxXGreater = maxX >= otherMaxX;
Bfloat maxY = origin.y + height;
float otherMaxY = otherRect.origin.y + otherRect.height;
BOOL maxYGreater = maxY >= otherMaxY;
return originBelow && maxXGreater && maxYGreater;
}
- (BOOL)isEqualToRectangle:(Rectangle *)otherRect {
BOOL sizeEqual = ((width == otherRect.width) &&
(height == otherRect.height));
return sizeEqual && [origin isEqualToXYPoint:otherRect.origin];
}
Note: I didn't test these, just pasted them together from the conditions of your ifs, so double-check them before you use them. I did fix the typo, though.
Notice that I also made up a method on XYPoint here, isEqualToXYPoint:; you can implement that as well, to return a BOOL if the x and y of both XYPoints are equal.
For some reason, I can't get this program to work. I've had other CS majors look at it and they can't figure it out either.
This program performs the Jacobi algorithm (you can see step-by-step instructions and a MATLAB implementation here). BTW, it's different from the Wikipedia article of the same name.
Since NSArray is one-dimensional, I added a method that makes it act like a two-dimensional C array. After running the Jacobi algorithm many times, the diagonal entries in the NSArray (i[0][0], i[1][1], etc.) are supposed to get bigger and the others approach 0. For some reason though, they all increase exponentially. For instance, i[2][4] should equal 0.0000009, not 9999999, while i[2][2] should be big.
Thanks,
Chris
NSArray+Matrix.m
#implementation NSArray (Matrix)
#dynamic offValue, transposed;
- (double)offValue {
double sum = 0.0;
for ( MatrixItem *item in self )
if ( item.nonDiagonal )
sum += pow( item.value, 2.0 );
return sum;
}
- (NSMutableArray *)transposed {
NSMutableArray *transpose = [[[NSMutableArray alloc] init] autorelease];
int i, j;
for ( i = 0; i < 5; i++ ) {
for ( j = 0; j < 5; j++ ) {
[transpose addObject:[self objectAtRow:j andColumn:i]];
}
}
return transpose;
}
- (id)objectAtRow:(NSUInteger)row andColumn:(NSUInteger)column {
NSUInteger index = 5 * row + column;
return [self objectAtIndex:index];
}
- (NSMutableArray *)multiplyWithMatrix:(NSArray *)array {
NSMutableArray *result = [[NSMutableArray alloc] init];
int i = 0, j = 0, k = 0;
double value;
for ( i = 0; i < 5; i++ ) {
for ( j = 0; j < 5; j++ ) {
value = 0.0; // (JeremyP's answer)
for ( k = 0; k < 5; k++ ) {
MatrixItem *firstItem = [self objectAtRow:i andColumn:k];
MatrixItem *secondItem = [array objectAtRow:k andColumn:j];
value += firstItem.value * secondItem.value;
}
MatrixItem *item = [[MatrixItem alloc] initWithValue:value];
item.row = i;
item.column = j;
[result addObject:item];
}
}
return result;
}
#end
Jacobi_AlgorithmAppDelegate.m
// ...
- (void)jacobiAlgorithmWithEntry:(MatrixItem *)entry {
MatrixItem *b11 = [matrix objectAtRow:entry.row andColumn:entry.row];
MatrixItem *b22 = [matrix objectAtRow:entry.column andColumn:entry.column];
double muPlus = ( b22.value + b11.value ) / 2.0;
muPlus += sqrt( pow((b22.value - b11.value), 2.0) + 4.0 * pow(entry.value, 2.0) );
Vector *u1 = [[[Vector alloc] initWithX:(-1.0 * entry.value) andY:(b11.value - muPlus)] autorelease];
[u1 normalize];
Vector *u2 = [[[Vector alloc] initWithX:-u1.y andY:u1.x] autorelease];
NSMutableArray *g = [[[NSMutableArray alloc] init] autorelease];
for ( int i = 0; i <= 24; i++ ) {
MatrixItem *item = [[[MatrixItem alloc] init] autorelease];
if ( i == 6*entry.row )
item.value = u1.x;
else if ( i == 6*entry.column )
item.value = u2.y;
else if ( i == ( 5*entry.row + entry.column ) || i == ( 5*entry.column + entry.row ) )
item.value = u1.y;
else if ( i % 6 == 0 )
item.value = 1.0;
else
item.value = 0.0;
[g addObject:item];
}
NSMutableArray *firstResult = [[g.transposed multiplyWithMatrix:matrix] autorelease];
matrix = [firstResult multiplyWithMatrix:g];
}
// ...
Have you got any unit tests for your matrix category? I mean, are you certain that the multiplication algorithm works? I would say that initialising value to 0 happens in the wrong loop. I think you need to do it inside the j loop.
A couple of other observations:
You don't need the #dynamic property declaration because you are defining the implementation of the properties yourself.
Consider creating your own Matrix class that wraps a normal C array of doubles. You might find the implementation a bit simpler.
When you add the square root term to muPlus, you don't divide by two. The calculation should be either:
double muPlus = ( b22.value + b11.value ) / 2.0;
muPlus += sqrt( pow((b22.value - b11.value), 2.0)
+ 4.0 * pow(entry.value, 2.0)
) / 2.0;
or:
double muPlus = ( b22.value + b11.value );
muPlus += sqrt( pow((b22.value - b11.value), 2.0)
+ 4.0 * pow(entry.value, 2.0) );
muPlus /= 2.0;
Also, you assign u1.y to both Gr,c and Gc,r. From the algorithm description, you want Gr,c=U1,2 (or u1.y) and Gc,r=U2,1 (or u2.x). Note that you don't actually need u2; you can substitute -u1.y for u2.x and u1.x for u2.y.
Off-Topic
According to the Fundamental Rule of Cocoa Memory Management, -[NSArray multiplyWithMatrix:] should return an autoreleased array, since the multiplicand should relinquish ownership. Also, you should use accessors to assign GT * A * G to matrix rather than doing it directly so that it can be properly managed.
Since most of the tests in the loop to fill out g will be false during each iteration, it's most likely more efficient to fill g with some default values and then update g. You could create a zero matrix, then set the diagonal to ones, then fill in the values from U, or you could create an identity matrix (leave the i%6 == 0 test in the loop) then fill in the values from U. Profile each of the three approaches.