iPhoneアプリ開発、号外 こんにちはAR

テーマ:

 お久しぶり!

 iOS 11リリースされましたねえ。

 でもってXcode 9もリリースされたんで、ARサンプル公開。

 

 

 

 

サンプル:

http://tetera.jp/xcc/book-sample/ARDemo.zip

 

 ていっても、DAEファイル組み込んでるだけっすけどね。

 さっすがになあ、今朝ツィートしたAT-Aふにゃふにゃのサンプルは、おっちゃん、怖あて、アップでけんけどな。要はこのサンプルのDAEファイル差し替えて、大きさと位置調整すればいけますわ。

 

 1).objとか.3ds形式のフリーの3Dモデルをネットでゲット

 2)Blenderでインポートして調整

 3).dae形式でエクスポート

 

や。頑張ってなあ。

 

 てことでサンプルをちょい解説。

 XcodeのテンプレでAR選んで

 

 

 テンプレで用意されたプロジェクトフォルダにある、art.scnassetsフォルダの中のship.scn、texture.pngを削除してgirl.daeファイルを配置。

 ↓ こいつね

 

 

 ViewController.swifでgirl.daeを読み込んで大きさ調整して1m手前に表示させてるだけ。とっても簡単だ。

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set the view's delegate
        sceneView.delegate = self
        
↓いらないので削除
        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true
        
        // Create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        
↓追加
        // Create a new scene
        let scene = SCNScene()
        let url = Bundle.main.url(forResource: "art.scnassets/girl", 
                withExtension: "dae")!
        guard let girlScene = try? SCNScene(url:url) else { return }
        for child in girlScene.rootNode.childNodes {
            scene.rootNode.addChildNode(child)
        }
        
        let girl = scene.rootNode.childNode(withName: "Armature", 
                recursively: true)!
        girl.scale = SCNVector3(x:girl.scale.x * 0.1, 
                y:girl.scale.y * 0.1, z:girl.scale.z * 0.1)
        girl.position = SCNVector3(x:0, y:-1, z:-1)

        // Set the scene to the view
        sceneView.scene = scene
    }

 てことで、いつか機会があれば説明もします。

 じゃまた。

 

AD

iPhoneアプリ開発、その(240) ヒエログリフのグリフ

テーマ:
 暗号だと思った?
 残念Core Textちゃんでした。
 せっかくCore Text扱ってるので、文字の形状を定義したベジェ曲線を取り出して使ってみマッスル。
 いわゆるグリフ(Glyph:字体)というやつです。
 例えば、世界には同じ「A」を意味する図形がいろいろあるわけですが、この図形それぞれを「A」のグリフというわけですYooo


「A」を意味するいろいろなグリフちゃん
0


 で、OS XやiOSでは、このグリフがベジェ曲線でフォントごとに提供されてまして、Core Textを使うと、指定したフォントから指定した文字のベジェ曲線が取り出せるようになってるんですな。
注意)ベジェ曲線は、数個の座標点を指定して定義する曲線で、iOSやOS Xで曲線を描くのに使う。これ常識な→(iPhoneアプリ開発、その(229) ワン・フロム・ザ・ハート)。
 それが

CTFontCreatePathForGlyph

 で、こいつに取り出したい文字と、どのフォントから取り出したいかを指定することで、指定した文字に対応するグリフが取り出せるようになっている。

 取り出したい文字は番号で指定します。
 この番号はフォントごとに固有であることには注意が必要。
 例えばASCIIコードやunicodeでは、アルファベットの「A」を数値の65と定義してるんですが、これをそのまま番号として渡しても「A」のグリフを指定したことにはなりません。
 グリフを指定するための番号はフォントごとに固有に決められていて、その値を知るためには

CTFontGetGlyphsForCharacters

 を使えってことになってます。
 こいつに、どのフォントかと、どの文字かを指定することで、知りたいグリフ番号を取り出せます。
 ここでようやく文字の指定としてunicodeを使うことになる。
 unicodeは今のところ符号なし16ビット整数で指定します。UInt16型でもいいんだけど、UniCharという型が用意されているんでそっちを使いましょう。NSUIntegerみたいに将来変更されるかもしれんでな。
 unicodeの値も、グリフの番号も同じく符号なし16ビット整数なんだけど、グリフの番号の型はCGGlyph型として用意されているんでこっちを使う。グリフを指定する番号、ちゅ~意味でCGGlyphなんでしょう。
 あと、フォントの指定にUIFontは使えません。
 Core TextはCTFontという専用のオブジェクトが用意されていて、そっちを使います。
 UIFontが示すフォントと同じものを、CTFontととして作りたい場合に使うのは

CTFontCreateWithFontDescriptor

 swiftでは

func CTFontCreateWithFontDescriptor(_ descriptor: CTFontDescriptor, _ size: CGFloat, _ matrix: UnsafePointer) -> CTFont

と定義されていて、第1引数descriptorにはCTFontDescriptorを渡す必要があるけど、こいつにはUIFontから-fontDescriptorメッセージで取り出したUIFontDescriptorをそのまま渡せます。
 sizeには文字のポイント数を指定し、matrixには取り出したグリフの座標系を変更したい時に指定する。そのまま使うならnilを指定。これで新しく作成されたCTFontが戻される。

fontは事前に用意したUIFont
let ctfont = CTFontCreateWithFontDescriptor(font.fontDescriptor(), font.pointSize, nil)

 これでCTFontGetGlyphsForCharactersを使ってグリフ番号を手に入れる準備は完了。例えばアルファベットの「A」のグリフ番号を手に入れたいなら、「A」を意味するunicodeは65なので、上で紹介したCTFontGetGlyphsForCharacters にCTFontと65を渡せば、「A」のグリフを取り出すための番号が手に入る。
 このCTFontGetGlyphsForCharacters、swiftでは

func CTFontGetGlyphsForCharacters(_ font: CTFont, _ characters: UnsafePointer, _ glyphs: UnsafeMutablePointer, _ count: CFIndex) -> Bool

と定義されていて、第1引数fontにCTFontを渡し、次のcharactersにUniChar型の変数の番地か、UniChar型配列の先頭番地を渡すことになってます。
 具体的には配列だと

var unicodes:[UniChar] = [65, 66, 67]
var glyphs:[CGGlyph] = [0, 0, 0]
CTFontGetGlyphsForCharacters(ctfont, &unicodes, &glyphs, unicodes.count)

なんてすることで、「A」、「B」、「C」それぞれのグリフ番号が一気に手に入ることになる。
 成功するとtrue、失敗するとfalseが戻ります。

 ちなみにUniCharの配列であるunicodesは、UnsafePointerとして渡すわけで、これは指し示される内容を変更しないという意味なので、varじゃなくletで用意してもいいんじゃないかと思ったけど実際やると怒られます。
 受け取る方のCGGlyph配列であるglyphsはUnsafeMutablePointerで正真正銘ポイント先の内容が変更されるのでvarにする必要がある。
 上ではCGGlyph配列をリテラル表記で[0,0,0]と3個分羅列して用意してるけど、init(count:, repeatedValue:)を使って用意してもかまいません。ここらへんは好き好きで。初期値なんて設定してもしょうがないけどswiftの決まりなんで0で初期化。あとはcountに確保したい要素数を指定する。今回ならunicodesの要素数分必要なんでunicodes.countを使う。

var glyphs = [CGGlyph](count:unicodes.count, repeatedValue:0)

 それと、ここでは「A」のunicode値として直接65って指定してるけど「A」がunicodeの値でいくつなのかを知る方法もあります。

let unicode = UniChar("A".unicodeScalars.first!.value) unicodeに65が入る

 ま、それは別の機会で触れるとして、CTFontGetGlyphsForCharactersから戻されたCGGlyph配列の番号を使えば、それぞれのベジェ曲線をCTFontCreatePathForGlyphで取り出せます。こいつは

func CTFontCreatePathForGlyph(font: CTFont, _ glyph: CGGlyph, _ matrix:
  UnsafePointer) -> CGPath?

と定義されていて、第1引数fontにはCTFont、次のglyphに番号を渡せばCGPathが戻される。
 matrixはCTFontCreateWithFontDescriptorと同じなのでnilを指定。
 例えば、さっきの「A」、「B」、「C」を取り出したglyphsならglyphs[1]とすることで、「B」のベジェ曲線が戻されるってわけです。

CTFontCreatePathForGlyph(ctfont, glyphs[1], nil)

 戻り値はCGPathに?が付いてる事からわかりますがオプショナルです。パスが作れなかった場合はnilが戻されるので、その点のチェックも忘れないように。
 サンプルでは指定したunicode1文字のパスを取り出すメソッドを用意して使っています。

サンプル:https://github.com/Takahiro-Kunii/TextToPath.gitのBranch:v1


// 指定したfontから、指定したunicodeに対応するCGPathを取り出す
func unicodeToCGPath(var unicode:UniChar, font:UIFont) -> CGPath? {
// 取り出しにはCore TextのAPIを使うのでUIFontではなくCTFontを用意しなければいけない
let ctfont = CTFontCreateWithFontDescriptor(font.fontDescriptor(), font.pointSize, nil)

// CTFontが管理する文字形状(CGPath)群は、そのCTFont独自のインディックスで
// 管理される。このインディックスはCGGlyph型として定義されていて、指定した
// unicode列に対応するCGGlyph列を返すAPI
// CTFontGetGlyphsForCharacters
// を使い取り出せる

var glyph:CGGlyph = 0 // 指定されたunicodeに対応するインディックス
if CTFontGetGlyphsForCharacters(ctfont, &unicode, &glyph, 1) {
// インディックス(CGGlyph)が取り出せたので、これを指定して対応する
// 文字形状(CGPath)を取り出す

return CTFontCreatePathForGlyph(ctfont, glyph, nil)
}

// インディックスが取り出せなかった(指定したフォントに、指定したunicodeの
// 文字形状が用意されていない等)

return nil // 失敗
}

 上のglyphのように、1文字なら、CGGlyph配列使わずにCGGlyph変数を使うこともできる。
 サンプルでは取り出したベジェ曲線を枠線を描いたり、塗りつぶしたりできるCALayer派性クラスのオブジェクト

CAShapeLayer

に設定して画面に表示させてます。
 あとオプショナルが戻されるので、アンラップしてnilでない時だけ実行するようにしてる。

if let path = self.unicodeToCGPath(0x65, font:font) {

 このif let path = てのが、unicodeToCGPathからの戻り値、CGPath?をアンラップしてnilでないか確認する作業。nilでなければpathにCGPathが設定される。

if let path = self.unicodeToCGPath(0x65, font:font) {
let shape = CAShapeLayer()
shape.path = path // ベジェ曲線設定
shape.frame = CGPathGetBoundingBox(path) // 矩形設定(レイヤなので
// 設定しなくても表示されるが…)


// サブレイヤーとして登録
self.view.layer.addSublayer(shape)

// 位置調整 ほぼ真ん中に配置
shape.position = self.view.center
}

 ちなみにiOSと文字のベジェ曲線の座標系が違うんで上下逆転してる。
1
 ま、それは後で対応するとして、グリフ形状のCAShapeLayerが作れたことで何ができるかというと画面のマスキングができるんですな。
 例えばUIImageViewなんかで画像を表示しておいて、このUIImageViewのlayerのmaskプロパティに指定するとこうなる。
2
 でもって、CAShapeLayerはCALayer派性なんでアニメーションもでき。
 前回のその(239)で使った領域指定にも使える、はず。
 というところで、以下次回!
AD