Created
July 18, 2014 09:30
-
-
Save kazukitanaka0611/0345947b53bff7899b93 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Edge* MakeEdge(NSUInteger i, NSUInteger j); | |
@interface Edge : NSObject <NSCopying> | |
@property (nonatomic, assign, readonly) NSUInteger i; | |
@property (nonatomic, assign, readonly) NSUInteger j; | |
+ (instancetype)edgeWithI:(NSUInteger)i J:(NSUInteger)j; | |
@end | |
@interface Edge() | |
@end | |
Edge* MakeEdge(NSUInteger i, NSUInteger j) | |
{ | |
return [Edge edgeWithI:i J:j]; | |
} | |
@implementation Edge | |
+ (instancetype)edgeWithI:(NSUInteger)i J:(NSUInteger)j | |
{ | |
return [[self alloc] initWithI:i J:j]; | |
} | |
- (instancetype)initWithI:(NSUInteger)i J:(NSUInteger)j | |
{ | |
if (self = [super init]) | |
{ | |
_i = i; | |
_j = j; | |
} | |
return self; | |
} | |
- (BOOL)isEqual:(id)object | |
{ | |
if (object == self) | |
{ | |
return YES; | |
} | |
if (!object || ![[object class] isEqual:[self class]]) | |
{ | |
return NO; | |
} | |
return [self isEqualToEdge:object]; | |
} | |
- (BOOL)isEqualToEdge:(Edge *)edge | |
{ | |
if (self == edge) | |
{ | |
return YES; | |
} | |
if (edge == nil) | |
{ | |
return NO; | |
} | |
if (self.i == edge.i && self.j == edge.j) | |
{ | |
return YES; | |
} | |
if (self.i == edge.j && self.j == edge.i) | |
{ | |
return YES; | |
} | |
return NO; | |
} | |
- (NSUInteger)hash | |
{ | |
NSUInteger hash = self.i; | |
hash = hash * 31u + self.j; | |
return hash; | |
} | |
- (id)copyWithZone:(NSZone *)zone | |
{ | |
Edge *copy = [[[self class] allocWithZone:zone] init]; | |
if (copy != nil) | |
{ | |
copy->_i = _i; | |
copy->_j = _j; | |
} | |
return copy; | |
} | |
@end | |
#import "MyScene.h" | |
#import "Edge.h" | |
@interface MyScene() | |
@property (nonatomic, strong) NSMutableDictionary *vertexes; | |
@property (nonatomic, strong) NSMutableDictionary *connections; | |
@property (nonatomic, strong) NSMutableArray *mutableEdges; | |
@property (nonatomic, assign) CGFloat repulsion; | |
@property (nonatomic, assign) CGFloat attraction; | |
@property (nonatomic, assign) BOOL contentCreated; | |
@property (nonatomic, assign) BOOL stable; | |
@property (nonatomic, strong) SKNode *touchedNode; | |
@end | |
@implementation MyScene | |
-(id)initWithSize:(CGSize)size | |
{ | |
if (self = [super initWithSize:size]) | |
{ | |
/* Setup your scene here */ | |
self.connections = [NSMutableDictionary dictionary]; | |
self.vertexes = [NSMutableDictionary dictionary]; | |
self.mutableEdges = [NSMutableArray array]; | |
self.repulsion = 600.f; | |
self.attraction = 0.1f; | |
[self addEdge]; | |
} | |
return self; | |
} | |
#pragma mark - touch event | |
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event | |
{ | |
UITouch *touch = touches.anyObject; | |
CGPoint positionInScene = [touch locationInNode:self]; | |
NSArray *nodes = [self nodesAtPoint:positionInScene]; | |
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(SKNode *node, NSDictionary *bindings) { | |
return [node.name isEqualToString:@"circle"]; | |
}]; | |
SKNode *node = [nodes filteredArrayUsingPredicate:predicate].firstObject; | |
self.touchedNode = node; | |
} | |
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event | |
{ | |
UITouch *touch = touches.anyObject; | |
CGPoint positionInScene = [touch locationInNode:self]; | |
self.touchedNode.position = positionInScene; | |
} | |
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event | |
{ | |
self.touchedNode = nil; | |
} | |
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event | |
{ | |
self.touchedNode = nil; | |
} | |
#pragma mark - update | |
-(void)update:(CFTimeInterval)currentTime | |
{ | |
if (self.stable) | |
{ | |
return; | |
} | |
for (NSUInteger i = 0; i < self.vertexes.count; i++) | |
{ | |
SKShapeNode *v = self.vertexes[@(i)]; | |
SKShapeNode *u; | |
CGFloat vForceX = 0; | |
CGFloat vForceY = 0; | |
for (NSUInteger j = 0; j < self.vertexes.count; j++) | |
{ | |
if (i == j) continue; | |
u = self.vertexes[@(j)]; | |
double rsq = pow(v.position.x - u.position.x, 2) + | |
pow(v.position.y - u.position.y, 2); | |
vForceX += self.repulsion * (v.position.x - u.position.x) / rsq; | |
vForceY += self.repulsion * (v.position.y - u.position.y) / rsq; | |
} | |
for (NSUInteger j = 0; j < self.vertexes.count; j++) | |
{ | |
if (![self.mutableEdges containsObject:MakeEdge(i, j)]) | |
{ | |
continue; | |
} | |
u = self.vertexes[@(j)]; | |
vForceX += self.attraction * (u.position.x - v.position.x); | |
vForceY += self.attraction * (u.position.y - v.position.y); | |
} | |
v.physicsBody.linearDamping = 0.95; | |
v.physicsBody.velocity = CGVectorMake((v.physicsBody.velocity.dx + vForceX), | |
(v.physicsBody.velocity.dy + vForceY)); | |
[v.physicsBody applyForce:CGVectorMake(vForceX, vForceY)]; | |
v.physicsBody.angularVelocity = 0; | |
} | |
[self updateConnections]; | |
} | |
#pragma mark didMoveToView | |
- (void)didMoveToView:(SKView *)view | |
{ | |
if (!self.contentCreated) | |
{ | |
self.backgroundColor = [SKColor blueColor]; | |
self.physicsWorld.gravity = CGVectorMake(0, 0); | |
self.contentCreated = YES; | |
} | |
} | |
#pragma mark didChangeSize | |
- (void)didChangeSize:(CGSize)oldSize | |
{ | |
[super didChangeSize:oldSize]; | |
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame]; | |
} | |
#pragma mark - private method | |
- (void)updateConnections | |
{ | |
[self.connections enumerateKeysAndObjectsUsingBlock:^(Edge *key, SKShapeNode *connection, BOOL *stop) { | |
CGMutablePathRef pathToDraw = CGPathCreateMutable(); | |
SKNode *vertexA = self.vertexes[@(key.i)]; | |
SKNode *vertexB = self.vertexes[@(key.j)]; | |
CGPathMoveToPoint(pathToDraw, NULL, vertexA.position.x, vertexA.position.y); | |
CGPathAddLineToPoint(pathToDraw, NULL, vertexB.position.x, vertexB.position.y); | |
connection.path = pathToDraw; | |
CGPathRelease(pathToDraw); | |
}]; | |
} | |
- (void)addEdge | |
{ | |
NSArray *edges = @[ | |
MakeEdge(0, 1), | |
MakeEdge(1, 2), | |
MakeEdge(2, 3), | |
MakeEdge(3, 0), | |
MakeEdge(3, 4), | |
MakeEdge(4, 5), | |
MakeEdge(4, 6), | |
MakeEdge(4, 7), | |
MakeEdge(4, 8), | |
]; | |
for (Edge *edge in edges) | |
{ | |
[self.mutableEdges addObject:edge]; | |
[self createVertexIfNeeded:edge.i]; | |
[self createVertexIfNeeded:edge.j]; | |
[self createConnectionForEdge:edge]; | |
} | |
} | |
- (void)createConnectionForEdge:(Edge *)edge | |
{ | |
SKShapeNode *connection = [SKShapeNode node]; | |
connection.strokeColor = [SKColor redColor]; | |
connection.fillColor = [SKColor redColor]; | |
connection.lineWidth = 3.f; | |
[self addChild:connection]; | |
self.connections[edge] = connection; | |
} | |
- (void)createVertexIfNeeded:(NSUInteger)index | |
{ | |
if (self.vertexes[@(index)] == nil) | |
{ | |
SKShapeNode *circle = [self createVertexNode]; | |
NSInteger maxWidth = (NSInteger)(self.size.width ? : 1); | |
NSInteger maxHeight = (NSInteger)(self.size.height ? : 1); | |
circle.position = CGPointMake(arc4random() % maxWidth, | |
arc4random() % maxHeight); | |
[self addChild:circle]; | |
self.vertexes[@(index)] = circle; | |
} | |
} | |
- (SKShapeNode *)createVertexNode | |
{ | |
SKShapeNode *node = [SKShapeNode node]; | |
node.zPosition = 10; | |
node.physicsBody.allowsRotation = NO; | |
node.name = @"circle"; | |
CGFloat diameter = 40; | |
CGRect circleRect = CGRectMake(- diameter / 2, -diameter / 2, | |
diameter, diameter); | |
node.path = CGPathCreateWithEllipseInRect(circleRect, nil); | |
node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:diameter / 2]; | |
return node; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment