頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


4要素のうちの3要素で頂点座標データ(SCNGeometrySourceオブジェクト)を作る

sizeof(simd_float3)=16という罠
↑この続き。
ARFaceGeometryクラスのverticesプロパティでsimd_float3型の頂点データ列を入手できるが、この型はfloat値を4つ持つ。
このデータ列を使ってなんとか頂点データを作成し、点を描きたい。

(x0 , y0 , z0 , 0.)
(x1 , y1 , z1 , 0.)
(x2 , y2 , z2 , 0.)

↑このようにfloat値が並んでいるsimd_float3から4要素目を無視して頂点データ列を作る必要がある。
自分で頂点数分のループを回してデータを作成、、、なんてことをしなくてもありがたいことに使えるメソッドがある。
これを使い、SceneKitで点を描くクラスメソッドを作成した。

SceneKitで点を描く

↑元はこちらにあるコードで、SCNGeometrySourceオブジェクトの作り方だけが下のように変わる。
+(SCNNode *)makeDotsNodeFor_simd_float3:(simd_float3 *)positions nofDots:(NSUInteger)nofDots dotRadius:(float)dotRadius
{
    //SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:(const SCNVector3 *)positions count:nofDots];// —(1)

    NSData *data = [NSData dataWithBytes:positions length:sizeof(float)*4*nofDots];// —(2)
    
    SCNGeometrySource *vertexSource = [SCNGeometrySource
                                       geometrySourceWithData:data
                                       semantic:SCNGeometrySourceSemanticVertex
                                       vectorCount:nofDots
                                       floatComponents:YES
                                       componentsPerVector:3
                                       bytesPerComponent:sizeof(float)
                                       dataOffset:0
                                       dataStride:sizeof(float)*4];// —(3)
    …
}

{
    // 呼び出し元
    …
    const simd_float3 *vertices = arFaceGeometry.vertices;
    SCNNode *dotsNode = [MyUtility makeDotsNodeFor_simd_float3:(simd_float3 *)vertices nofDots:arFaceGeometry.vertexCount dotRadius:15.0];
    …
}
(1)は修正前のコード。SCNVector3が並んでいるデータならこれでOK。
(2)SCNGeometrySourceオブジェクトを作成するのに頂点データ列を渡すのにNSData型にする必要があるので作成する。
(3)でdataStrideをfloat値4個分としているので4個ごとに取得してくれる。

実行結果がこちら↓
↑頂点が正しく表示された。


スポンサーサイト



sizeof(simd_float3)=16という罠

ARKitのARFaceGeometryから顔の頂点を描く
↑この続き。
なんか頂点の表示がおかしいのでは?と調べていたら

sizeof(simd_float3)の出力結果

↑sizeof(simd_float3)が16ということに気付いた、、、!!!

16バイト?!

float値は32bitで4バイト。それが4つ分?3つじゃないだと?

どうやらsimd_float3はfloat値が3つではなく4つ分のサイズらしい。

simd_float3のヘッダー

↑リファレンスにはそんなこと書いていないけど、ヘッダーにはそう書いてる(時々あるよね)。

簡単な確認をする。
{
    simd_float3 vertices[] = {
        simd_make_float3(1.0 , 2.0 , 3.0) ,
        simd_make_float3(4.0 , 5.0 , 6.0) ,
        simd_make_float3(7.0 , 8.0 , 9.0) ,
        simd_make_float3(10.0 , 11.0 , 12.0)
    }; // —(1)

    for (NSUInteger ite=0;ite<4;ite++) { //—(2)
        simd_float3 vertex = vertices[ite];
        NSLog(@"vertex[%ld] (%f,%f,%f)", ite , vertex.x,vertex.y,vertex.z);
    }

    
    float *fcv = (float *)vertices;// float-casted vertices —(3) 
    for (NSUInteger ite=0;ite<4;ite++) { // —(4) 
        NSLog(@"fcv%ld (%f,%f,%f)", ite , fcv[ite*3+0],fcv[ite*3+1],fcv[ite*3+2]);
    }
}
(1)simd_float3型の頂点を4個作成してverticesとする。
(2)のようにして作成したデータを確認する。
(3)floatにキャストする。
(4)キャストしたfloat値を3個ずつ表示する。

その結果がこちら↓

実行結果

↑キャストした場合、4要素目にゼロが入っているのがわかる。
この場合、4つに1つしかまともな頂点座標を示していない。
なるほど、それで前記事では頂点が正しく描画できなかったわけだ。

続く。





ARKitのARFaceGeometryから顔の頂点を描く

フロントカメラが得た画像を自分の顔のテクスチャ用にシェーダに渡したい(3)
↑この流れ。iPhoneのフロントカメラ(TrueDepth Camera)で遊ぶ。

ARKitのARFaceGeometryクラスにverticesというプロパティがある。
@property(nonatomic, readonly) const simd_float3 *vertices;
↑このようにsimd_float3で頂点の位置情報を取得できるらしい、と聞けばやはり表示させてみたい!と思うのでやってみた。

ARSCNViewDelegateメソッド内に記述する、とする。顔の頂点のアップデートやテクスチャをシェーダに渡す処理などは省略して点表示に関わる部分のコードがこんな感じ↓
-(void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    if (dotsNode) {//—(1)
        [dotsNode removeFromParentNode];
    }

    ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor;
    ARFaceGeometry *arFaceGeometry = faceAnchor.geometry;//—(2)
    //NSLog(@"arFaceGeometry.vertexCount = %ld",arFaceGeometry.vertexCount);//—(3)
    const simd_float3 *vertices = arFaceGeometry.vertices;//—(4)

    dotsNode = [MyUtility makeDotsNodeForSCNVectors:(SCNVector3 *)vertices nofDots:arFaceGeometry.vertexCount dotRadius:15.];//—(5)
    dotsNode.geometry.firstMaterial.diffuse.contents = [UIColor grayColor];
    [faceNode addChildNode:dotsNode];//—(6)

}
ここでdotsNodeはSCNNodeオブジェクト。
このdelegateメソッドがコールされるたびに(1)でdotsNodeを画面から消して、その後に描く処理を行っている。
(2)でARFaceGeometryオブジェクトを得ているが、、、変数名が微妙なのは自分でもわかっているのだが、ARSCNFaceGeometryクラスなんてのもあってそれぞれどうしたものか?と自分の中でまだ定まっていなかったりする。
頂点数は(3)のようなvertexCountプロパティで取得できる。現在のバージョンだと1220個。なおここでNSLogするとズラーっとコンソールに表示されてしまう。
(4)ここで頂点データを取得できる。
(5)は前記事で作成した点を描画する自作メソッド(※1)だ。SCNVector3*型にキャストしている。そうしないと
Incompatible pointer types sending 'const simd_float3 *' to parameter of type 'SCNVector3 *' (aka 'struct SCNVector3 *')
↑このような警告が出るので。まあどちらもfloat値がx,y,zの順に並んでいて単に先頭のポインタを渡しているだけだ。
(6)faceNodeは
- (SCNNode *)renderer:(id)renderer nodeForAnchor:(ARAnchor *)anchor;
メソッド内で指定する顔のSCNNodeオブジェクト。このオブジェクト、というかARFaceAnchorオブジェクト基準の座標系での座標を得ているのでこのfaceNodeにaddChildNode:する。

その結果がこちら↓ ↑やたー!表示できた!と喜び勇んでツイートしたんだけど、、、なんか頂点少なくね?左右対称じゃないし?どこかがおかしい、、、?

実際間違えていたんだけど、長くなるので続く。


※1 前記事はこちら↓
SceneKitで点を描く





SceneKitで点を描く

SceneKitで直線を描く
↑この続きというか関連記事。
直線の次は点を描く。
+(SCNNode *)makeDotsNodeForSCNVectors:(SCNVector3 *)positions nofDots:(NSUInteger)nofDots dotRadius:(float)dotRadius
{
    SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:nofDots];
    SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:nil primitiveType:SCNGeometryPrimitiveTypePoint primitiveCount:nofDots bytesPerIndex:0];
    
    element.pointSize = 2.0*dotRadius;//—(1)
    element.minimumPointScreenSpaceRadius = dotRadius;//—(2)
    element.maximumPointScreenSpaceRadius = dotRadius;//—(3)
    
    SCNGeometry *dots = [SCNGeometry geometryWithSources:@[vertexSource] elements:@[element]];
    
    SCNNode *dotsNode = [SCNNode nodeWithGeometry:dots];
    return dotsNode;
}

{
    //呼び出す側
    SCNVector3 positions[] = { SCNVector3Make(-3.0 , 3.0 , 0.0) , SCNVector3Make(3.0 , 3.0 , 0.0) };
    SCNNode *dotsNode = [MyUtility makeDotsNodeForSCNVectors:positions nofDots:2 dotRadius:30.0];
    dotsNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor];
}
↑elementを作るときのデータにnilを渡し、bytesPerIndexも0にしてる。なぜこれで動くんだという感じだが、、、まあ、座標も個数もわかってるから描けるじゃん、と言えば描けるんだが。
前記事の直線の場合と違って点の場合は大きさを指定できる、というかmin…,max…などを指定しないと表示されない。全ての点を同じ大きさで表示したいので、(1)〜(3)を同じにしている。

点描画の実行結果

↑実行結果。半径30としている。


SceneKitで直線を描く

SceneKitにはSCNGeometryのサブクラスに
SCNPlane, SCNBox, SCNSphere, SCNPyramid, SCNCone, SCNCylinder, SCNCapsule, SCNTube, and SCNTorus(※1)
などがあってこれらを使えば簡単に平面や直方体などを作ることができる。

ところが、なぜか点と線がない。

主にデバッグ用に点や線を描きたくなる時があるので、使いやすいようにSCNNodeを返すようなクラスメソッドを作成した。

まずは直線から。なお太さは指定できない。
+(SCNNode *)makeLineNodeForSCNVectors:(SCNVector3 *)positions
{
    int indicies[] = {0,1};
    NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
    
    SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:2];
    SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeLine primitiveCount:2 bytesPerIndex:sizeof(int)];
    SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource] elements:@[element]];
    
    SCNNode *lineNode = [SCNNode nodeWithGeometry:line];

    return lineNode;
}

{
    //呼び出す側
    SCNVector3 positions[] = {  SCNVector3Make(-3.0 , 3.0 , 0.0) , SCNVector3Make(3.0 , 3.0 , 0.0) };
    SCNNode *lineNode = [MyUtility makeLineNodeForSCNVectors:positions];
    lineNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor];
}
↑2点を与えそれらを結ぶ直線を描く。
SCNGeometryPrimitiveTypeLineで2点だとどういう形状か確定するためか、indexDataがnilでも動く。そういうものなのか。

あと、座標を表すのにSCNVectorを使っているけどSCNVectorは
typedef struct SCNVector3 {
    float x, y, z;
} SCNVector3;
というように3つのfloat値を持つ構造体として定義されてるので、
{
    float *positions = calloc(6, sizeof(float));
    positions[0]  = -3.0;
    positions[1]  = 3.0;
    positions[2]  = 0.0;
    positions[3]  = 3.0;
    positions[4]  = 3.0;
    positions[5]  = 0.0;
}
↑このようにfloat値を並べて置いてそのポインタを渡してもOK(ただしキャストしないと警告が出る)。

直線描画のスクリーンショット

↑実行結果、、、あれ?スクショをトリミングして縮小したら線が見えなくなった?

↑動画をTwitterにアップしてみた。かろうじて見えるかも。


※1 公式リファレンスはこちら↓
SCNGeometry - SceneKit | Apple Developer Documentation







Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.