Cocoa練習帳 -9ページ目

[OSX][iOS][Android]cocos2d-xのAndroid環境を整える

前回のインストールの経験からAndroid環境を見直した。


ApplicationsディレクトリにDevelopmentディレクトリを用意して、そこにcocos2d-xとAndroid関連のツールを配置する事にした。


$ cd /Applications/Development
$ pwd
/Applications/Development
$ ls -l
lrwxr-xr-x  adt-bundle-mac-x86_64 -> adt-bundle-mac-x86_64-20140702
drwxrwxrwx@ adt-bundle-mac-x86_64-20140702
lrwxr-xr-x  android-ndk -> android-ndk-r10b
drwxr-xr-x@ android-ndk-r10b
lrwxr-xr-x  apache-ant -> apache-ant-1.9.4
drwxr-xr-x@ apache-ant-1.9.4
lrwxr-xr-x  cocos2d-x -> cocos2d-x-3.3beta0
drwxr-xr-x@ cocos2d-x-3.3beta0

Apache ANTはhttp://ant.apache.org/bindownload.cgiから入手した。

バージョンが変わる度に設定を変更するのは大変だが、ディレクトリ名を変更してしまうとバージョンが分からなくなるのでシンボリックリンクを作成した。


そして、cocos2d-xの設定を実行する。


$ cd /Applications/Development/cocos2d-x
$ ./setup.py
$ source ~/.bash_profile

Android NDKとSDK、APache ANTのパスが聞かれるはずで、著者の場合は以下を入力した。


    ->Added NDK_ROOT=/Applications/Development/android-ndk
    ->Added ANDROID_SDK_ROOT=/Applications/Development/adt-bundle-mac-x86_64/sdk
    ->Added ANT_ROOT=/Applications/Development/apache-ant/bin

すると、.bash_profileの内容は、以下の感じになるはずだ。


$ cd
$ cat .bash_profile
PATH="$PATH:~/bin"

# Add environment variable COCOS_CONSOLE_ROOT for cocos2d-x
export COCOS_CONSOLE_ROOT=/Applications/Development/cocos2d-x-3.3beta0/tools/cocos2d-console/bin
export PATH=$COCOS_CONSOLE_ROOT:$PATH

# Add environment variable NDK_ROOT for cocos2d-x
export NDK_ROOT=/Applications/Development/android-ndk
export PATH=$NDK_ROOT:$PATH

# Add environment variable ANDROID_SDK_ROOT for cocos2d-x
export ANDROID_SDK_ROOT=/Applications/Development/adt-bundle-mac-x86_64/sdk
export PATH=$ANDROID_SDK_ROOT:$PATH
export PATH=$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/platform-tools:$PATH

# Add environment variable ANT_ROOT for cocos2d-x
export ANT_ROOT=/Applications/Development/apache-ant/bin
export PATH=$ANT_ROOT:$PATH

これで準備ができたはずなので、前回同様、プロジェクトを作成してみよう。


$ cd ~/Documents/Development
$ cocos new MyGame -p jp.co.bitz.mygame -l cpp -d Projects

Androidのビルドを試してみよう。


$ cd Projects/MyGame/proj.android
$ ./build_native.py
Couldn't find the gcc toolchain.

あれ?失敗する。


気を取り直して、このプロジェクトをExclipseでimportしてみよう。


すると多数のプロジェクトが候補として現れるが、libcocos2dxと、今回生成したMyGameのみを選んで、後のチェックは外す。


ビルドしてみる。


[2014-09-24 22:45:17 - Dex Loader] Unable to execute dex: Multiple dex files define Lorg/cocos2dx/lib/Cocos2dxAccelerometer;
[2014-09-24 22:45:17 - MyGame] Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define Lorg/cocos2dx/lib/Cocos2dxAccelerometer;

あれ、失敗した。もしかしたら、NDKのバージョンが問題?


関連情報

【Cocoa練習帳】
http://ameblo.jp/bitz/(ミラー・サイト)
Cocos2d-X Game Development Essentials/Packt Publishing
¥2,559
Amazon.co.jp

[Android]マルチスクリーンに対応する

iOSのiPhoneとiPadの両方に対応したMaster-DetailアプリケーションのAndroid版だ。

サンプルのアプリケーション名は、WebViewを使ったアプリケーションということでNeXTSTEP上で開発された世界で最初のWebブラウザと同じ名前にした。


AndroidManifest.xmlでは、WebViewを使うので通信を有効にするのと、スマートフォンの画面サイズでは、詳細画面でWebViewを表示するので詳細画面アクティビティを宣言する。


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nexus"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".DetailActivity" />
    </application>

</manifest>

スマートフォンとタブレットの画面サイズで、主アクティビティの表示内容を変えるため、主アクティビティのXMLファイルは、res/layoutとres/layout-largeの二種類を用意する。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<fragment
android:id="@+id/masterFragment"
android:name="com.example.nexus.MasterFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    
<fragment
android:id="@+id/masterFragment"
android:tag="masterFragment"
android:name="com.example.nexus.MasterFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.0" />
<FrameLayout
   android:id="@+id/detailPane"
   android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2.0" />
    
</LinearLayout>

主アクティビティでは、画面サイズの判定結果をメンバ変数で保持する。


package com.example.nexus;
 
import android.support.v7.app.ActionBarActivity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
 
public class MainActivity extends ActionBarActivity {
static boolean isLargeScreen = false;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
isLargeScreen = isLargeScreen();
}
 
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
 
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
 
public boolean isLargeScreen() {
int layout = getResources().getConfiguration().screenLayout;
return (layout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE; 
}
}

一覧のXMLファイル。表示するサイト毎にボタンを用意。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Index"
        android:textSize="50sp"
         />
<Button
   android:id="@+id/button_google"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Google"
   />
<Button
   android:id="@+id/button_bing"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bing"
   />
</LinearLayout>

一覧のフラグメントのソースコード。画面サイズ毎に処理を分けている。


package com.example.nexus;
 
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
 
@SuppressLint("NewApi")
public class MasterFragment extends Fragment implements OnClickListener {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_master, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button mButtonGoogle = (Button) getActivity().findViewById(R.id.button_google);
mButtonGoogle.setOnClickListener(this);
 
Button mButtonBing = (Button) getActivity().findViewById(R.id.button_bing);
mButtonBing.setOnClickListener(this);
}
public void onClick(View v) {
if (MainActivity.isLargeScreen) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
DetailFragment detailFragment = new DetailFragment();
Bundle args = new Bundle();
switch (v.getId()) {
case R.id.button_google:
args.putString("URL", "http://www.google.com/");
break;
case R.id.button_bing:
args.putString("URL", "http://bing.com/");
break;
}
detailFragment.setArguments(args);
transaction.replace(R.id.detailPane, detailFragment);
transaction.addToBackStack(null);
transaction.commit();
}
else {
Intent intent;
switch (v.getId()) {
case R.id.button_google:
intent = new Intent(getActivity(), DetailActivity.class);
intent.putExtra("URL",  "http://www.google.com/");
           getActivity().startActivity(intent);
           break;
case R.id.button_bing:
intent = new Intent(getActivity(), DetailActivity.class);
intent.putExtra("URL",  "http://bing.com/");
           getActivity().startActivity(intent);
break;
}
}
}
}

WebViewを配置して詳細画面のXMLファイル。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
<WebView 
   android:id="@+id/detailWebView"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
</LinearLayout>

詳細画面のフラグメント。


package com.example.nexus;
 
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
public class DetailFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState) {
super.onCreateView(inflater, container, saveInstanceState);
View view = inflater.inflate(R.layout.fragment_detail,  container, false);
WebView webView = (WebView)view.findViewById(R.id.detailWebView);
webView.setWebViewClient(new WebViewClient());
String url = getArguments().getString("URL");
webView.loadUrl(url);
return view;
}
}

詳細画面のアクティビティ版のXMLファイル。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <WebView 
   android:id="@+id/detailWebView"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
 
</LinearLayout>

対応するソースコード。


package com.example.nexus;
 
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
public class DetailActivity extends Activity {
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        
        WebView webView = (WebView)findViewById(R.id.detailWebView);
        webView.setWebViewClient(new WebViewClient());
        String url = getIntent().getStringExtra("URL");
webView.loadUrl(url);
    }
}

エミュレータの調子が悪いため、画面ダンプがとれなかった。残念。


ソースコード
GitHubからどうぞ。

関連情報

【Cocoa練習帳】
http://ameblo.jp/bitz/(ミラー・サイト)
プログラミングAndroid/オライリージャパン
¥3,672
Amazon.co.jp
改訂2版 Android SDK逆引きハンドブック/シーアンドアール研究所
¥4,644
Amazon.co.jp

[OSX][iOS][Android]cocos2d-xをインストールする

著者の場合、OS XとiOS、Androidでの開発を考えているので、事前に以下をインストールしておいた。

著者は、NDKを"/Applications/Android/NDK/android-ndk-r10"に配置した。このパスを.bash_profileに追加した。

NDKはバージョンアップされるたびにモノが公開され、モノ毎にディレクトリ名が異なるようだ。NDKのパスをcocos2d-xに設定する為、"android-ndk-r10"を"android-ndk"に名前を変えて、"/Applications/Android"直下に置くことも考えたが、今回はこのようにした。


$ cat .bash_profile
export PATH=$PATH:/Applications/Android/NDK/android-ndk-r10

そして、cocos2d-xを入手する。

cocos2d-xの置き場所だが、著者は"/Users/yukio/Documents/Development/cocos2d-x-3.2"に置いた。

次に、cocos2d-xの設定だ。


$ cd /Users/yukio/Documents/Development/cocos2d-x-3.2
$ python download-deps.py
$ ./setup.py
$ source ~/.bash_profile

Android SDKとNDKのパスが聞かれると思うので、さっき、配置したパスを入力する。

新規プロジェクトを作ってみる。


$ cd ~/Documents/Development
$ cocos new MyGame -p jp.co.bitz.mygame -l cpp -d Projects

OS XとiOSは、これでプロジェクトファイルが出来ているので、直に試せるが、Androidはまだまだ設定があるが、それはまたの機会に。

【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[OSX]gnuplotをインストールする

自動制御の勉強をする為、gnuplotをインストールした。その手順を自分自身への備忘録として投稿する。

■X11
XQuartz

■gnuplot
gnuplot

組み込みのreadlineを使用する事にした。また、コマンド履歴を有効にした。


$ tar -xvzf gnuplot-4.6.5.tar.gz

$ cd gnuplot-4.6.5

$ ./configure --with-readline=builtin --enable-history-file

$ make
$ sudo make install
関連情報
XQuartzプロジェクト
gnuplot

【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[iOS]画像から動画を生成する

複数の画像と音声データから動画の生成を試している。画像から動画が動いたので当初の目的は達成されていないが記事にする。

基本的に、参考情報のブログ記事をまねさせて貰っている。ありがとう。

独自にカスタマイズしてハマったところ。一つは、AVAssetWriterのインスタンスはプロパティで保持しておいて処理が完了するまでリリースされないようにしないと、処理が途中で終わる。出力先の動画ファイル名は同じものにしているので、既に存在していたら削除しているが、ファイル出力の直前に削除すると、出力が失敗した。

画像にノイズが入るバグがあるが解決していない。

渡された画像を一秒間隔でスライドショーしているが、この裏で音声データを埋め込みたい。その方法は調査中だ。

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
 
@interface ViewController ()
@property (strong, nonatomic) AVAssetWriter *movieAssetWriter;
@property (readonly) NSArray                *images;
@property (readonly) CGSize                 imageSize;
@property (readonly) NSString               *moviePath;
- (void)_photoToMovie;
- (CVPixelBufferRef)_pixelBufferFromCGImage:(CGImageRef)image;
- (void)_removeMovieFile;
- (void)_saveMovie;
- (void)_completionHandlerWithVideo:(NSString *)videoPath
           didFinishSavingWithError:(NSError *)error
                        contextInfo:(void *)contextInfo;
@end
 
@implementation ViewController
 
- (IBAction)photoToMovie:(id)sender
{
    [self _photoToMovie];
}
 
- (NSArray *)images
{
    return @[[UIImage imageNamed:@"one"],
             [UIImage imageNamed:@"two"],
             [UIImage imageNamed:@"three"],
             [UIImage imageNamed:@"four"]];
}
 
-(CGSize)imageSize
{
    /* 全画像のサイズが同一を想定している */
    return ((UIImage *)self.images[0]).size;
}
 
- (NSString *)moviePath
{
    /* 出力ファイルのパスを作成 */
    NSArray     *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString    *documentPath = paths[0];
    NSString    *moviePath = [documentPath stringByAppendingPathComponent:@"photo2movie.mov"];
    DBGMSG(@"%s moviePath(%@)", __func__, moviePath);
    return moviePath;
}
 
- (void)_photoToMovie
{
    /* 既存の動画ファイルを削除 */
    [self _removeMovieFile];
    
    /* 動画出力インスタンスを生成 */
    NSError *error = nil;
    self.movieAssetWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:self.moviePath]
                                                             fileType:AVFileTypeQuickTimeMovie
                                                                error:&error];
    if (error) {
        DBGMSG(@"%@", [error localizedDescription]);
        return;
    }
    
    /* AVAssetWriterInputはCMSampleBufferRefでデータを受け取る */
    AVAssetWriterInput  *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                                               outputSettings:@{AVVideoCodecKey:AVVideoCodecH264,
                                                                                                AVVideoWidthKey:@(self.imageSize.width),
                                                                                                AVVideoHeightKey:@(self.imageSize.height)}];
    [self.movieAssetWriter addInput:assetWriterInput];
    
    /* AVAssetWriterInputPixelBufferAdaptorを使うとCVPixelBufferRefでデータを受け取れる */
    AVAssetWriterInputPixelBufferAdaptor    *assetWriterInputPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterInput
                                                                                                                                                   sourcePixelBufferAttributes:@{(NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_32ARGB),
                                                                                                                                                                                 (NSString *)kCVPixelBufferWidthKey:@(self.imageSize.width),
                                                                                                                                                                                 (NSString *)kCVPixelBufferHeightKey:@(self.imageSize.height)}];
    
    assetWriterInput.expectsMediaDataInRealTime = YES;
    
    /* 動画生成開始 */
    if (![self.movieAssetWriter startWriting]) {
        DBGMSG(@"Failed to start writing.");
        return;
    }
    
    [self.movieAssetWriter startSessionAtSourceTime:kCMTimeZero];
    
    int frameCount = 0;
    int durationForEachImage = 1;
    int32_t fps = 1;
    
    for (UIImage *image in self.images) {
        if (assetWriterInputPixelBufferAdaptor.assetWriterInput.readyForMoreMediaData) {
            CMTime frameTime = CMTimeMake((int64_t)frameCount * fps * durationForEachImage, fps);
            
            CVPixelBufferRef    pixelBufferRef = [self _pixelBufferFromCGImage:image.CGImage];
            
            if (![assetWriterInputPixelBufferAdaptor appendPixelBuffer:pixelBufferRef withPresentationTime:frameTime]) {
                DBGMSG(@"Failed to append buffer. [image : %@]", image);
            }
            
            if(pixelBufferRef) {
                CVBufferRelease(pixelBufferRef);
            }
            
            frameCount++;
        }
    }
    
    // 動画生成終了
    [assetWriterInput markAsFinished];
    [self _saveMovie];
    CVPixelBufferPoolRelease(assetWriterInputPixelBufferAdaptor.pixelBufferPool);
}
 
- (CVPixelBufferRef)_pixelBufferFromCGImage:(CGImageRef)imageRef
{
    NSDictionary *options = @{(NSString *)kCVPixelBufferCGImageCompatibilityKey:@(YES),
                              (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey:@(YES)};


    CVPixelBufferRef pxbuffer = NULL;


    CVPixelBufferCreate(kCFAllocatorDefault,
                        CGImageGetWidth(imageRef),
                        CGImageGetHeight(imageRef),
                        kCVPixelFormatType_32ARGB,
                        (__bridge CFDictionaryRef)options,
                        &pxbuffer);


    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);


    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata,
                                                 CGImageGetWidth(imageRef),
                                                 CGImageGetHeight(imageRef),
                                                 8,
                                                 4 * CGImageGetWidth(imageRef),
                                                 rgbColorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);


    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)), imageRef);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    
    return pxbuffer;
}
 
- (void)_removeMovieFile
{
    /* 既存の動画ファイルを削除 */
    NSError *error;
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.moviePath]) {
        [[NSFileManager defaultManager] removeItemAtPath:self.moviePath error:&error];
        if (error) {
            DBGMSG(@"%@", [error localizedDescription]);
        }
    }
}
 
- (void)_saveMovie
{
    /* 書き込み */
    [self.movieAssetWriter finishWritingWithCompletionHandler:^{
        DBGMSG(@"Finish writing!");
        self.movieAssetWriter = nil;
        
        UISaveVideoAtPathToSavedPhotosAlbum(self.moviePath, self, @selector(_completionHandlerWithVideo:didFinishSavingWithError:contextInfo:), NULL);
    }];
}
 
- (void)_completionHandlerWithVideo:(NSString *)videoPath
    didFinishSavingWithError:(NSError *)error
                 contextInfo:(void *)contextInfo
{
    DBGMSG(@"%s error:%@", __func__, error);
}
 
@end
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/PhotoToMovie - GitHub
関連情報 AV Foundationプログラミングガイド
iOS Tips #7 画像から動画を作成する | アドカレ2013 : SP #15
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[iOS]GLKitとOpenGL ES 2.0

以前、GLKitを使ったOpenGLを試した事があるが、あのときは、OpenGL ES 1.1だった。今回は、OpenGL ES 2.0だ。

参考図書『OpenGL ES 2.0 Programming Guide』の頭に、「Hello Triangle Example」というサンプルコードが紹介されている。それをGLKitを使って実装してみた。

プロジェクトは、XcodeのiOS/Application/OpenGL Gameで生成されるコードを残す形の実装となっている。

頂点フェーダは以下の内容に書き直す。

/* 頂点バッファvPositionを入力バッファに配置  */
attribute vec4 vPosition;


void main()
{
    /* vPositionを頂点座標に設定*/
    gl_Position = vPosition;
}

フラグメント・フェーダは以下の内容に書き直す。

/* precision宣言 16bit(half) */
precision mediump float;


void main()
{
    /* 赤色を設定 */
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

GLKViewControllerのサブクラスViewControllerは以下のとおり。

#define BUFFER_OFFSET(i) ((char *)NULL + (i))
 
GLfloat gVVertexData[] = {
    0.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
};
 
@interface ViewController () {
    GLuint _program;
    
    GLuint _vertexArray;
    GLuint _vertexBuffer;
}
@property (strong, nonatomic) EAGLContext *context;
@property (strong, nonatomic) GLKBaseEffect *effect;
 
- (void)setupGL;
- (void)tearDownGL;
 
- (BOOL)loadShaders;
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file;
- (BOOL)linkProgram:(GLuint)prog;
- (BOOL)validateProgram:(GLuint)prog;
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    /* OpenGL ES 2.0コンテキストを生成 */
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    if (!self.context) {
        DBGMSG(@"Failed to create ES context");
    }
    
    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
    
    [self setupGL];
}


- (void)dealloc
{
    [self tearDownGL];
    
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    if ([self isViewLoaded] && ([[self view] window] == nil)) {
        self.view = nil;
        
        [self tearDownGL];
        
        if ([EAGLContext currentContext] == self.context) {
            [EAGLContext setCurrentContext:nil];
        }
        self.context = nil;
    }
    
    // Dispose of any resources that can be recreated.
}
 
- (void)setupGL
{
    /* コンテキストを設定 */
    [EAGLContext setCurrentContext:self.context];
    
    [self loadShaders];
    
    /* 頂点配列を生成し結合する */
    glGenVertexArraysOES(1, &_vertexArray);
    glBindVertexArrayOES(_vertexArray);
    
    /* バッファオブジェクトを生成し、結合後、頂点データを設定 */
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(gVVertexData), gVVertexData, GL_STATIC_DRAW);
    
    /* 頂点属性配列を有効化し、バッファと関連づける */
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
    
    /* 頂点配列の結合を解除 */
    glBindVertexArrayOES(0);
}
 
- (void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
    
    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteVertexArraysOES(1, &_vertexArray);
    
    self.effect = nil;
    
    if (_program) {
        glDeleteProgram(_program);
        _program = 0;
    }
}
 
#pragma mark - GLKView and GLKViewController delegate methods
 
/* 今回は動かないので空。 */
- (void)update
{
}
 
/* 描画。GLKViewデリゲートのメソッド。 */
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    /* カラーバッファをクリア */
    glClear(GL_COLOR_BUFFER_BIT);
    
    /* シャーダが含まれるプログラムオブジェクトを設定 */
    glUseProgram(_program);
    
    /* 頂点配列を結合する */
    glBindVertexArrayOES(_vertexArray);
    
    /* GL_TRIANGLESプリミティブで描画 */
    glDrawArrays(GL_TRIANGLES, 0, 3);
}
 
#pragma mark -  OpenGL ES 2 shader compilation
 
- (BOOL)loadShaders
{
    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
    
    /* プログラム・オブジェクトの生成 */
    _program = glCreateProgram();
    
    /* 頂点シェーダとフラグメント・シェーダのファイルを読み込み、コンパイルする */
    // Load the vertex/fragment shaders
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
        DBGMSG(@"Failed to compile vertex shader");
        return NO;
    }
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
        DBGMSG(@"Failed to compile fragment shader");
        return NO;
    }
    
    /* シェーダ・オブジェクトを登録 */
    glAttachShader(_program, vertShader);
    glAttachShader(_program, fragShader);
    
    /* 頂点シェーダ属性をGLKVertexAttribPosition(0)に結合する */
    glBindAttribLocation(_program, GLKVertexAttribPosition, "vPosition");
    
    /* プログラム・オブジェクトに関連づける */
    if (![self linkProgram:_program]) {
        DBGMSG(@"Failed to link program: %d", _program);
        
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (_program) {
            glDeleteProgram(_program);
            _program = 0;
        }
        
        return NO;
    }
    
    // Release vertex and fragment shaders.
    if (vertShader) {
        glDetachShader(_program, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(_program, fragShader);
        glDeleteShader(fragShader);
    }
    
    return YES;
}
 
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    GLint status;
    const GLchar *source;
    
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source) {
        DBGMSG(@"Failed to load vertex shader");
        return NO;
    }
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        DBGMSG(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return NO;
    }
    
    return YES;
}
 
- (BOOL)linkProgram:(GLuint)prog
{
    GLint status;
    
    // Link the program
    glLinkProgram(prog);
    
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        DBGMSG(@"Program link log:\n%s", log);
        free(log);
    }
#endif
    
    // Check the link status
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0) {
        GLint infoLen = 0;
        glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &infoLen);
        if (1 < infoLen) {
            char *infoLog = malloc(sizeof(char) * infoLen);
            glGetProgramInfoLog(_program, infoLen, NULL, infoLog);
            DBGMSG(@"%s Error linking program:\n%s\n", __func__, infoLog);
            free(infoLog);
        }
        return NO;
    }
    
    return YES;
}
 
- (BOOL)validateProgram:(GLuint)prog
{
    GLint logLength, status;
    
    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        DBGMSG(@"Program validate log:\n%s", log);
        free(log);
    }
    
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}
 
@end
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/HelloTriangle - GitHub
関連情報 iOS OpenGL ES プログラミングガイド
OpenGL ES 2.0 Programming Guide

OpenGL ES 2.0 Programming Guide/Addison-Wesley Professional
¥価格不明
Amazon.co.jp
OpenGL ES 2.0 Programming Guide/Addison-Wesley Professional
¥価格不明
Amazon.co.jp
Open GL ES 2.0 プログラミングガイド/ピアソン桐原
¥5,616
Amazon.co.jp
OpenGL® ES 2.0 Programming Guide/Addison-Wesley Professional
¥6,678
Amazon.co.jp

関東第67回Cocoa勉強会

夏休み期間中という事で席に若干の余裕があった勉強会だった。

発表は「Swiftでアプリを再実装」と「新しいIBキーワード」、「Markdownパーサを比較してみる」の三本。時間に余裕があった為、色々と議論できた事と、直に役立った情報が得られたのが嬉しかった。

関連情報 Cocoa勉強会
見学申し込み
connpass
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[OSX][iOS]Swift演算子

代入演算子
代入式に論理演算が含まれているのがC言語と異なる点かな?

= *= /= %= += -= <<= >>= &= ^= |= &&= ||=

算術演算子
オーバーフローを無視する演算子の導入が新しい。

+ - * / % &+ &- &* &/ &% &

前置/後置演算子
インクリメント(++)とデクリメント(--)で、C言語と同様に変数の前に置く場合と、後ろに置く場合で返す値が異なる。

++ --

比較演算子
オブジェクトの比較(===と!==)とパターンマッチ(~=)が新しい。ただし、パターンマッチについては演算子の説明があるが利用方法の説明はない。これは将来の直接的な正規表現の対応の為に予約されているのか?

< <= > >= == != === !== ~=

三項演算子
式1が真なら式2が評価され、偽なら式3が評価される。

式1 ? 式2 : 式3

論理演算子

! && ||

ビット演算子

<< >> & | ^ ~

範囲演算子
新しく導入された演算子だ。Closed Range Operator(A...B)はA以上からB以下の範囲。Half-Closed Range Operator(A..<B)はA以上からB未満の範囲という意味だ。

... ..<

キャスト演算子
新しく導入された演算子だ。isは型チェック。asはキャスト。as?はOptional型へのキャスト。

is as as?

演算子の再定義は別の機会に。

関連情報 プログラミング言語C 第2版 ANSI規格準拠
Cocoa in a Nutshell
Objective‐C
The Swift Programming Language
Using Swift with Cocoa and Objective-C
Swift離陸ガイド
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
note

[OSX][iOS]Swiftリテラル

整数リテラル

読みやすさの為に任意の位置に"_"を挿入する事ができる。値としては"_"は無視される。

2進数
let numMax = 0b010011010010
var num = 0b010011010010

8進数
let numMax = 0o2322
var num = 0o2322

10進数
let numMax = 1234
var num = 1234

16進数
let numMax = 0x04D2
var num = 0x04D2


浮動小数点リテラル

読みやすさの為に任意の位置に"_"を挿入する事ができる。値としては"_"は無視される。

let a = 125.0
let b = 1.25e2



let c = 0.0125
let d = 1.25e-2




let e = 60.0
let f = 0x0Fp2




let g = 3.75

let h = 0x0Fp2
文字列リテラル
"文字列"

エスケープ列
NUL文字(\0)
バックスラッシュ(\\)
水平タブ(\t)
改行(\n)
復帰(\r)
2重引用符(\")
単一引用符(\')

文字列に変換
\(式)

16進指定
1バイト文字(\x12)
2バイト文字(\u1234)
4バイト文字(\U12345678)

関連情報
プログラミング言語C 第2版 ANSI規格準拠
Cocoa in a Nutshell
Objective‐C
The Swift Programming Language
Using Swift with Cocoa and Objective-C
Swift離陸ガイド

【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
note

[OSX][iOS]Swift構文規約

改行までは一つの文。C言語であった終端を示すセミコロン(;)は不要。

コメント(注釈)

コメントはK&R Cと同様に/*で始め、*/で終わる記法と、C++の//から改行までの両方に対応する。

識別子(名前)

英字と数字、それに加え、一部制限があるがUnicodeに対応しているので日本語も使える。ただし、最初の文字は数字以外でなければならない。

キーワード(予約語)

次の識別子はキーワードとして処理系によって予約されている。

class, deinit, enum, extension, func, import, init, let, protocol, static, struct, subscript, typealias, var

break, case, continue, default, do, else, fallthrough, if, in, for, return, switch, where, while

as, dynamicType, is, new, super, self, Self, Type, __COLUMN__, __FILE__, __FUNCTION__, __LINE__

associativity, didSet, get, infix, inout, left, mutating, none, nonmutating, operator, override, postfix, precedence, prefix, right, set, unowned, unowned(safe), unowned(unsafe), weak, willSet


関連情報
プログラミング言語C 第2版 ANSI規格準拠
Cocoa in a Nutshell
Objective‐C
The Swift Programming Language
Using Swift with Cocoa and Objective-C
Swift離陸ガイド

【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
note