ルービックキューブ 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からクラスのdrawCubesが呼ばれ、drawCubesは各<Cube>のdrawCubeを呼ぶという仕掛けです。

- (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の完成です。