taoru's memo

Objective-c,cocos2d,など開発についてのメモ(andoroidも少しだけ)

kmVec2でのベクトル計算

Kazmath - Kazade/kazmath

cocos2dにも標準で入っているライブラリKazmathの勉強をする。

今回は特にkmVec2に関するメソッドを色々と試す。
以前にも一度ベクトル計算を試している(※)が、kmVec2Dotとか何をしているか分かっていない。
2Dでベクトル計算ができると色々と捗りそうだったので、この機会にメソッドの効果をメモしておく。

※ベクトル計算使ってる記事
CCNodeのカテゴリで、他のCCNodeをゆったり追うSoftFollowというのを作ってみた その2 - 追記2/26 - taoru's memo

kmVec2メソッド
length(kmVec2) x,yの二乗を足した値の平方根を返す(大きさ)
lengthSq(kmVec2) x,yの二乗を足した値を返す
normalize(kmVec2&) 単位ベクトルを返す
add(kmVec2, kmVec2) 2ベクトルの加算結果を返す
sub(kmVec2, kmVec2) 2ベクトルの減算結果を返す
dot(kmVec2, kmVec2) 2ベクトルの内積を返す
scale(kmVec2&, kmScalar) 指定ベクトルをkmScalar倍する
equal(kmVec2, kmVec2) 2ベクトルが等しければtrueを返す
transform(kmVec2, kmMat3) 指定ベクトルを行列変換する

計算後のベクトルを表示したかったので、適当に表示用のメソッドをまず作った

- (void)displaySegmentWithVec:(kmVec2)vec color:(ccColor3B)color
{
    CCDrawNode *drawNode   = (CCDrawNode *)[self getChildByTag:0];
    if (!drawNode) {
        drawNode = [CCDrawNode node];
        drawNode.position = ccp(self.contentSize.width*0.5f, self.contentSize.height*0.5f);
        [self addChild:drawNode z:0 tag:0];
    }

    // fixedVecにvecを50倍したベクトルを代入
    kmVec2 fixedVec;
    kmVec2Scale(&fixedVec, &vec, 50.0f);
    CGPoint vecPoint = ccp(fixedVec.x, fixedVec.y);
    
    // (0,0)から(vec.x,vec.y)までの線分を描画
    [drawNode drawSegmentFrom:CGPointZero to:vecPoint radius:1.5f color:ccc4FFromccc3B(color)];

    // 元のVectorを表示
    CCLabelTTF *pointLabel = [CCLabelTTF labelWithString:@"" fontName:@"Helvetica" fontSize:16];
    pointLabel.position = ccpAdd(drawNode.position, ccp(vecPoint.x, vecPoint.y + 10));
    pointLabel.string   = NSStringFromCGPoint(ccp(vec.x, vec.y));
    pointLabel.color    = color;
    [self addChild:pointLabel z:1];
}

では補完が出る順(名前順)に試していく。

kmVec2Add

kmVec2* kmVec2Add(kmVec2* pOut, const kmVec2* pV1, const kmVec2* pV2)
{
	pOut->x = pV1->x + pV2->x;
	pOut->y = pV1->y + pV2->y;

	return pOut;
}

2ベクトルの加算

        kmVec2 vec1  = (kmVec2){1, 1};
        kmVec2 vec2  = (kmVec2){2, 0};
        
        kmVec2 resultVec;
        
        kmVec2Add(&resultVec, &vec1, &vec2);
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:vec2 color:ccRED];
        [self displaySegmentWithVec:resultVec color:ccWHITE];

白が計算結果(resultVec)

kmVec2AreEqual

int kmVec2AreEqual(const kmVec2* p1, const kmVec2* p2)
{
	return (
				(p1->x < p2->x + kmEpsilon && p1->x > p2->x - kmEpsilon) &&
				(p1->y < p2->y + kmEpsilon && p1->y > p2->y - kmEpsilon)
			);
}

ベクトルの比較( are なのは複数形ってこと?)

        kmVec2 vec1  = (kmVec2){1, 1};
        kmVec2 vec2  = (kmVec2){1, 1};
        BOOL isEqualVec = kmVec2AreEqual(&vec1, &vec2);
        CCLOG(@"isEqualVec = %d",isEqualVec); // 1 (YES) が出力される

kmVec2Dot

kmScalar kmVec2Dot(const kmVec2* pV1, const kmVec2* pV2)
{
    return pV1->x * pV2->x + pV1->y * pV2->y;
}

ベクトルの内積値を計算して返す。
"ドット"ってなんだよって思ってたけど、ドット積(点乗積、内積)のことらしい。
(wikipedia)ドット積
内積を表す式が[A・B]だからか…
内積は計算にcosθが含まれているので、2ベクトルのなす角を得たり直行判定に使える。

        kmVec2 vec1  = (kmVec2){1, 1};
        kmVec2 vec2  = (kmVec2){0, 2};
        
        float vecDot = kmVec2Dot(&vec1, &vec2);
        CCLOG(@"vec1とvec2の内積 = %f",vecDot);  // 2.0が出力される

kmVec2Fill

kmVec2* kmVec2Fill(kmVec2* pOut, kmScalar x, kmScalar y)
{
    pOut->x = x;
    pOut->y = y;
    return pOut;
}

ベクトルの代入. ただの代入なのでコード割愛.

kmVec2Length

kmScalar kmVec2Length(const kmVec2* pIn)
{
    return sqrtf(kmSQR(pIn->x) + kmSQR(pIn->y));
}

ベクトルの大きさを返す.
kmVec2LengthSqというメソッドもあるが、そちらは sqrtfする前の値を返す

kmVec2Normalize

kmVec2* kmVec2Normalize(kmVec2* pOut, const kmVec2* pIn)
{
	kmScalar l = 1.0f / kmVec2Length(pIn);

	kmVec2 v;
	v.x = pIn->x * l;
	v.y = pIn->y * l;

	pOut->x = v.x;
	pOut->y = v.y;

	return pOut;
}

大きさ1の単位ベクトルを計算して返す。
もっと早く知っておくべきだった…orz

        kmVec2 vec1  = (kmVec2){3, 2};

        kmVec2 resultVec;
        
        kmVec2Normalize(&resultVec, &vec1);
        CCLOG(@"resultVecの大きさ = %f", kmVec2Length(&resultVec)); // 1.0 が出力される
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:resultVec color:ccWHITE];

kmVec2Scale

kmVec2* kmVec2Scale(kmVec2* pOut, const kmVec2* pIn, const kmScalar s)
{
	pOut->x = pIn->x * s;
	pOut->y = pIn->y * s;

	return pOut;
}

ベクトルの大きさを指定倍する.

        kmVec2 vec1  = (kmVec2){1, 1};

        kmVec2 resultVec;
        
        kmVec2Scale(&resultVec, &vec1, 2.0f);
        CCLOG(@"resultVecの大きさ = %f", kmVec2Length(&resultVec)); // 2.828427 が出力される
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:resultVec color:ccWHITE];

kmVec2Subtract

kmVec2* kmVec2Subtract(kmVec2* pOut, const kmVec2* pV1, const kmVec2* pV2)
{
	pOut->x = pV1->x - pV2->x;
	pOut->y = pV1->y - pV2->y;

	return pOut;
}

2ベクトルの減算結果ベクトルを返す

        kmVec2 vec1  = (kmVec2){1, 2};
        kmVec2 vec2  = (kmVec2){3, 1};

        kmVec2 resultVec;
        
        kmVec2Subtract(&resultVec, &vec1, &vec2);
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:vec2 color:ccRED];
        [self displaySegmentWithVec:resultVec color:ccWHITE];

緑ベクトル − 赤ベクトル = 白ベクトル

kmVec2Transform

kmVec2* kmVec2Transform(kmVec2* pOut, const kmVec2* pV, const kmMat3* pM)
{
    kmVec2 v;

    v.x = pV->x * pM->mat[0] + pV->y * pM->mat[3] + pM->mat[6];
    v.y = pV->x * pM->mat[1] + pV->y * pM->mat[4] + pM->mat[7];

    pOut->x = v.x;
    pOut->y = v.y;

    return pOut;
}

ベクトルを行列で一次変換する.
数Bを真面目に勉強していなかったので辛い。
1時変換の参考にしたページ:■行列と1次変換

kmMat3は三次行列だけど、kmVec2は二次元なので2行3列しか使われていない(mat[2],mat[5],mat[8]を使ってない)

        kmVec2 vec1  = (kmVec2){1, 2};

        kmVec2 resultVec;
        
        // Y軸に関する対称移動
        kmMat3 mat3 = (kmMat3){
            -1, 0, 0,
             0, 1, 0,
             0, 0, 0
        };
        kmVec2Transform(&resultVec, &vec1, &mat3);
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:resultVec color:ccWHITE];

行列の知識がぶっ飛んでたので、とりあえず対称移動だけ頑張って思い出しました。

kmVec2TransformCoord

kmVec2* kmVec2TransformCoord(kmVec2* pOut, const kmVec2* pV, const kmMat3* pM)
{
    assert(0);
    return NULL;
}

使うとAsserionを吐いて落ちる。
訳がわからないよ。

kmVec3TransformCoordは実装されていたのでそっちを覗いてみたところ、
どうやらkmVec4Transform(1次元上の行列変換)にかけて、結果の4次元目で結果のxyzを割っている(?)

仮にkmVec3TransformCoordと同じようなロジックでkmVec2TransformCoordを実装するとしたら、
まずkazmath/vec3.h をincludeしてからこんな感じになるのだが…

kmVec2* kmVec2TransformCoord(kmVec2* pOut, const kmVec2* pV, const kmMat3* pM)
{
    kmVec3 v;
    kmVec3 inV;
    kmVec3Fill(&inV, pV->x, pV->y, 1.0);
    
    kmVec3Transform(&v, &inV,pM);
    
    pOut->x = v.x / v.z;
    pOut->y = v.y / v.z;
    
    return pOut;
}

kmVec3Transformの3番目の引数はkmMat4のため、このコードは動かない。
kmVec4Transformの3番目の引数はkmMat4のため、kmVec3TransformCoordは動く。

行列変換は難しい(小並感)

以上で kmVec2 を関するメソッドを全て試してみたことになる。
結構使えるかも?

参考ページ
平面幾何におけるベクトル演算 3.内積と外積
行列と一次変換

2ベクトルのなす角を求める

なす角を求めるには、arcCos( 内積 / (vec1の大きさ * vec2の大きさ) ) を計算すればよい

        kmVec2 vec1  = (kmVec2){1, 1};
        kmVec2 vec2  = (kmVec2){2, 0};

        // 内積
        float vecDot = kmVec2Dot(&vec1, &vec2);
        
        float vec1Length = kmVec2Length(&vec1);
        float vec2Length = kmVec2Length(&vec2);
        
        float radian = acosf(vecDot / (vec1Length * vec2Length));
        CCLOG(@"vec1 と vec2 のなす角 = %f",kmRadiansToDegrees(radian));
        
        [self displaySegmentWithVec:vec1 color:ccGREEN];
        [self displaySegmentWithVec:vec2 color:ccRED];

"vec1 と vec2 のなす角 = 45.0 が出力される

QLOOKアクセス解析