ルービックキューブ I - 6
ルービックキューブ Iもやっと最終回、今回は<MyWorld>クラスです。
<MyWorld>クラスは、<UIView>のサブクラスで、画面とRubicCubeの仲立ちをするクラスです。
<MyWorld.h>
#import <UIKit/UIKit.h> #import "RubicCube.h" #import "Ctm3d.h" @interface MyWorld : UIView { RubicCube *rubiccube; Ctm3d *ctm3d; CGPoint currMovePnt; } - (void)rotateX:(CGFloat)dx Y:(CGFloat)dy; @end
<MyWorld.h>はこれだけです。RubicCubeのインスタンスrubiccubeと、ワールド座標、ビュー座標を格納するCtm3dのインスタンスctm3d、それにタッチされた画面の座標を保存するcurrMovePntがインスタンス変数です。
<MyWorld.m>
#import "MyWorld.h" @implementation MyWorld - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // Initialization code ctm3d = [[Ctm3d alloc] initCtm3d]; rubiccube = [[RubicCube alloc] initRubicCube:ctm3d viewPoint:600.0]; //Viewpointの設定 [ctm3d addViewRotate:-M_PI/4.0 X:0.0 Y:10.0 Z:0.0]; [ctm3d addViewRotate:M_PI/8.0 X:10.0 Y:0.0 Z:0.0]; } return self; }
初期化メソッドinitWithFrameも単純です。
ctm3dとrubiccubeを初期化し、視点(Viewpoint)の設定を行っています。Viewpointはz軸上の+600.0とし、これにCtm3dのメソッドaddViewRotateで回転を与えています。この回転は、Ctm3dの(CATransform3D)viewに保存されます。
このルービックキューブ Iでは、画面をドラッグすることでルービックキューブ全体を回転させることができますが、
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint currentTouchPosition = [touch locationInView:self]; currMovePnt = currentTouchPosition; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSAutoreleasePool *arp = [[NSAutoreleasePool alloc] init]; UITouch *touch = [touches anyObject]; CGPoint currentTouchPosition = [touch locationInView:self]; CGFloat dx = currentTouchPosition.x - currMovePnt.x; CGFloat dy = currentTouchPosition.y - currMovePnt.y; [self rotateX:dx Y:dy]; currMovePnt = currentTouchPosition; [arp release]; [self setNeedsDisplay]; } - (void)isTouchesEnded:(int)no loc:(CGPoint)location { } - (void)rotateX:(CGFloat)dx Y:(CGFloat)dy { //dx,dyの反転に注意 Point3d *p = [[[Point3d alloc] Point3dMakeX:dy Y:dx Z:0.0] autorelease]; //ビュー座標をワールド座標に戻す Point3d *np = [ctm3d Point3dApplyCtm3dViewInvert:p]; CGFloat d = sqrt(dx*dx + dy*dy); [ctm3d addWorldRotate:(M_PI*d)/360.0 X:np.x Y:np.y Z:np.z]; }
touchesBeganは、ドラッグの始点をインスタンス変数currMovePntに保存するだけ。続くtouchesMovedでドラッグの移動量x,yをrotateX:Y:に渡しています。
rotateX:Y:では、渡されたx,yを加工してワールド座標上のベクトルに変換し、このベクトルを回転軸としてCtm3dのaddWorldRotateで回転を与えます。
コメントにもあるように、x方向のドラッグはy軸を中心とした回転に、y方向のドラッグはx軸を中心とした回転になりますので、回転軸を計算する際dx,dyを反転させています。また、この画面上の移動量はビュー座標上のものですから、Point3dApplyCtm3dViewInvertでワールド座標に変換しています。
rotateX:Y:でルービックキューブに回転を与えてから、touchesMovedは現在のドラッグ位置を保存し、setNeedsDisplay(UIViewオリジナルのメソッド)を呼んであとはシステムに任せます。
setNeedsDisplayが呼ばれ、再描画の必要があると判断するとシステムはdrawRectを実行します。これは先回りして前回説明しました。
drawRectから
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(context, 1.5); CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0); //ディスプレイ座標のセット CGAffineTransform xform; xform = CGAffineTransformMakeTranslation(320.0/2, 320.0/2); xform = CGAffineTransformScale(xform, 1.0, -1.0); CGContextConcatCTM(context, xform); [rubiccube drawCubes:context]; //後始末 xform = CGAffineTransformInvert(xform); CGContextConcatCTM(context, xform); } - (void)dealloc { [super dealloc]; } @end
<MyWorld>は以上です。
そうそう、最後に<MyWorld>をキックする部分が必要です。これは、View-based Applicationプロジェクトを作成した時に出来ている<RubicCubeViewController.m>(「RubicCube」の部分はプロジェクト名で異なります)のviewDidLoadに加えます。
- (void)viewDidLoad { [super viewDidLoad]; MyWorld *world = [[MyWorld alloc] initWithFrame:CGRectMake(0, 0, 320, 320)]; [world setBackgroundColor:[UIColor whiteColor]]; [self.view addSubview:world]; }
これでルービックキューブ Iの完成です。