目的
「
Android NDK で COLLADA dom ライブラリをビルド - TOP」でビルドした、COLLADA DOM ライブラリを、実際に使ってみる。
Android ネイティブアプリ作成の基本的なことは、習得済みという前提で、COLLADA DOM ライブラリを利用する部分だけを、ピックアップして説明する。
ただし、今回は、ファイルのロードと、セーブのみ。OpenGL ES での表示は、行わないこととする。
基本的な設定については、「
Android NDK で COLLADA DOM を使ってみる【基本設定編】」に記載してある。
モデルデータ
モデルデータは、Blender で生成したものを利用する。
他にも、「
COLLADA Model Bank」のサイトで、モデルデータをダウンロードするなどの手段がある。
ファイル名は、「sample.dae」とし、「assets」ディレクトリに格納することとする。
Makefile 作成
Android.mk ファイルの方は、COLLADA DOM のインクルードパスおよび、ライブラリパスが、フルパス指定になっているので、各自の環境に合わせて修正する必要がある。
【jni/Application.mk】
APP_MODULES := ColladaSandbox
APP_ABI := armeabi-v7a
APP_PLATFORM := android-9
APP_STL := gnustl_static
【jni/Android.mk】
LOCAL_PATH := $(call my-dir)
#
# ColladaSandbox
#
include $(CLEAR_VARS)
LOCAL_MODULE := ColladaSandbox
LOCAL_ARM_MODE := arm
LOCAL_CPPFLAGS := -fexceptions
# COLLADA_DOM_SUPPORT141 を定義しないと、DAE.load() 実行時に次のようなエラーとなる。
# 「libc(7688): Fatal signal 11 (SIGSEGV) at 0x656c6962 (code=1)」
LOCAL_CPPFLAGS += -DCOLLADA_DOM_SUPPORT141
LOCAL_SRC_FILES := $(wildcard *.c *.cpp)
LOCAL_C_INCLUDES := \
E:/work/Repositories/git/ColladaDom/ColladaDom/jni/include \
E:/work/Repositories/git/ColladaDom/ColladaDom/jni/include/1.4 \
E:/work/Repositories/git/ColladaDom/ColladaDom/jni/external-libs/boost
LOCAL_STATIC_LIBRARIES := \
android_native_app_glue
LOCAL_LDLIBS := \
-LE:/work/Repositories/git/ColladaDom/ColladaDom/obj/local/armeabi-v7a \
-lcollada_dom \
-lcollada_STLDatabase \
-lcollada_stdErrPlugin \
-lpcrecpp \
-lpcre \
-ltinyxml \
-lminizip \
-lboost_filesystem \
-lboost_system \
-llog \
-landroid \
-lEGL \
-lGLESv1_CM \
-lz \
-lgnustl_static
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
COLLADA DOM ログ出力
COLLADA DOM には、エラー出力機能をプラグインできる機能がある、デフォルトでは、標準出力にログが出る。
そのログを、LogCat に出力するように、プラグインを作成する。
ただ、今のところ、ログが出力されたところを、見たことが無い・・・
【jni/LogCatErrorHandler.h】
/*
* LogCatErrorHandler.h
*/
#ifndef LOGCATERRORHANDLER_H_
#define LOGCATERRORHANDLER_H_
#include <dae/daeErrorHandler.h>
namespace AndroidUtility {
class DLLSPEC LogCatErrorHandler: public daeErrorHandler {
public:
LogCatErrorHandler();
virtual
~LogCatErrorHandler();
public:
void
handleError(daeString msg);
void
handleWarning(daeString msg);
};
} /* namespace AndroidUtility */
#endif /* LOGCATERRORHANDLER_H_ */
【jni/LogCatErrorHandler.cpp】
/*
* LogCatErrorHandler.cpp
*/
#include <android/log.h>
#include <dae/daeTypes.h>
#include "LogCatErrorHandler.h"
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "LogCatErrorHandler", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "LogCatErrorHandler", __VA_ARGS__))
namespace AndroidUtility {
LogCatErrorHandler::LogCatErrorHandler() {
}
LogCatErrorHandler::~LogCatErrorHandler() {
}
void LogCatErrorHandler::handleError(daeString msg) {
LOGE("%s", msg);
}
void LogCatErrorHandler::handleWarning(daeString msg) {
LOGW("%s", msg);
}
}
/* namespace AndroidUtility */
メイン処理
メイン処理は、NDK に付属する「native_app_glue」を利用する、これは、「native_activity」をラッピングし、より手軽に実装できるようにしたもの。
実装は、NDK に付属してきたサンプルプロジェクト「native-activity」の「main.c」を変更して行った。
160行目あたりにサブ処理を呼び出す部分がある。
【jni/main.c】
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* AbsoluteArea has modified this file.
* The original license is provided above for reference and legal purposes.
*/
#include <jni.h>
#include <errno.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include "Sub.h"
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "main", __VA_ARGS__))
struct saved_state {
float angle;
int32_t x;
int32_t y;
};
struct engine {
struct android_app* app;
int animating;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
int32_t width;
int32_t height;
struct saved_state state;
};
static int engine_init_display(struct engine* engine) {
const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE,
8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE };
EGLint w, h, dummy, format;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format);
surface = eglCreateWindowSurface(display, config, engine->app->window,
NULL);
context = eglCreateContext(display, config, NULL, NULL);
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
LOGW("Unable to eglMakeCurrent");
return -1;
}
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
engine->display = display;
engine->context = context;
engine->surface = surface;
engine->width = w;
engine->height = h;
engine->state.angle = 0;
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
glDisable(GL_DEPTH_TEST);
return 0;
}
static void engine_draw_frame(struct engine* engine) {
if (engine->display == NULL) {
return;
}
glClearColor(((float) engine->state.x) / engine->width, engine->state.angle,
((float) engine->state.y) / engine->height, 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(engine->display, engine->surface);
}
static void engine_term_display(struct engine* engine) {
if (engine->display != EGL_NO_DISPLAY) {
eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
if (engine->context != EGL_NO_CONTEXT) {
eglDestroyContext(engine->display, engine->context);
}
if (engine->surface != EGL_NO_SURFACE) {
eglDestroySurface(engine->display, engine->surface);
}
eglTerminate(engine->display);
}
engine->animating = 0;
engine->display = EGL_NO_DISPLAY;
engine->context = EGL_NO_CONTEXT;
engine->surface = EGL_NO_SURFACE;
}
static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
struct engine* engine = (struct engine*) app->userData;
switch (cmd) {
case APP_CMD_SAVE_STATE:
engine->app->savedState = malloc(sizeof(struct saved_state));
*((struct saved_state*) engine->app->savedState) = engine->state;
engine->app->savedStateSize = sizeof(struct saved_state);
break;
case APP_CMD_INIT_WINDOW:
if (engine->app->window != NULL) {
engine_init_display(engine);
engine_draw_frame(engine);
}
break;
case APP_CMD_TERM_WINDOW:
engine_term_display(engine);
break;
case APP_CMD_LOST_FOCUS:
engine->animating = 0;
engine_draw_frame(engine);
break;
}
}
void android_main(struct android_app* state) {
struct engine engine;
app_dummy();
memset(&engine, 0, sizeof(engine));
state->userData = &engine;
state->onAppCmd = engine_handle_cmd;
engine.app = state;
if (state->savedState != NULL) {
engine.state = *(struct saved_state*) state->savedState;
}
/* COLLADA DOM LOAD AND SAVE */
init(state->activity->assetManager);
loadModel();
saveModel(state->activity->externalDataPath);
cleanup();
while (1) {
int ident;
int events;
struct android_poll_source* source;
while ((ident = ALooper_pollAll(engine.animating ? 0 : -1, NULL,
&events, (void**) &source)) >= 0) {
if (source != NULL) {
source->process(state, source);
}
if (state->destroyRequested != 0) {
engine_term_display(&engine);
return;
}
}
if (engine.animating) {
engine_draw_frame(&engine);
}
}
}
サブ処理
サブ処理には、COLLADA DOM の操作部分を記述してある。
【jni/Sub.h】
/*
* Sub.h
*/
#ifndef SUB_H_
#define SUB_H_
#include <android/asset_manager.h>
#ifdef __cplusplus
#include "LogCatErrorHandler.h"
using namespace AndroidUtility;
class Sub {
private:
DAE *dae;
AAssetManager *asset_manager;
public:
Sub(AAssetManager *asset_manager);
~Sub();
void loadModel();
void saveModel(const char *externalDataPath);
};
extern "C" {
#endif /* __cplusplus */
extern void init(AAssetManager *asset_manager);
extern void loadModel();
extern void saveModel(const char *externalDataPath);
extern void cleanup();
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* SUB_H_ */
【jni/Sub.cpp】
/*
* Sub.cpp
*/
#include <stdio.h>
#include <dae.h>
#include <dom/domCOLLADA.h>
#include <android/log.h>
#include <android/asset_manager.h>
#include "Sub.h"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "Sub", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "Sub", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "Sub", __VA_ARGS__))
using namespace AndroidUtility;
/*
* Sub class
*/
Sub::Sub(AAssetManager *asset_manager) {
this->asset_manager = asset_manager;
dae = new DAE();
}
Sub::~Sub() {
delete dae;
}
void Sub::loadModel() {
// Asset から、モデルデータを読み込む
AAsset *model = AAssetManager_open(asset_manager, "sample.dae", AASSET_MODE_UNKNOWN);
size_t size = AAsset_getLength(model);
char *buffer = new char[size + 1];
AAsset_read(model, buffer, size);
LOGI("Before geometry count [%d]", dae->getDatabase()->getElementCount(NULL, "geometry", NULL));
// 第一引数の URI は、データの識別子であり、unload の引数や、load、save 時のファイル名にもなる。
// ただし、第二引数に、読込み済みモデルデータを渡した場合、ファイルからではなく、第二引数のデータを利用する。
int iRet = dae->load("file:///sample.dae", buffer);
if(DAE_OK != iRet) {
// 読込みに失敗した場合
LOGE("Load error [%d]", iRet);
return;
}
LOGI("After geometry count [%d]", dae->getDatabase()->getElementCount(NULL, "geometry", NULL));
delete buffer;
AAsset_close(model);
}
void Sub::saveModel(const char *externalDataPath) {
// ICONIA TAB A700 の「externalDataPath」は、
// "/mnt/sdcard/Android/data/foobar.colladasandbox/files"になっている。
LOGI("externalDataPath=%s", externalDataPath);
char *uri;
// 保存場所は、環境に合わせて変更すること。
sprintf(uri, "file://%s%s", externalDataPath, "/../../../../sample2.dae");
LOGI("saveURI=%s", uri);
int iRet = dae->saveAs(uri, "file:///sample.dae", true);
LOGI("iRet [%d], %s", iRet, strerror(errno));
if(DAE_OK != iRet) {
// なぜだか、保存に失敗しても、DAE_OK が返ってくる。なんでだんべ・・・
LOGE("Failed to save the file.");
}
}
/*
* 以下、main.c から呼び出される関数
*/
Sub *sub;
extern "C" {
void init(AAssetManager *asset_manager) {
sub = new Sub(asset_manager);
// COLLADA DOM に、エラーハンドラを設定
// ただ、今のところ、メッセージが出力された所を、見たことが無い・・・
LogCatErrorHandler *errHandler = new LogCatErrorHandler();
daeErrorHandler::setErrorHandler(errHandler);
// ログを出力してみる
daeErrorHandler::get()->handleWarning("LogCatErrorHandler READY!!!");
}
void loadModel() {
sub->loadModel();
}
void saveModel(const char *externalDataPath) {
sub->saveModel(externalDataPath);
}
void cleanup() {
// アプリケーションが、終了する前の大掃除
DAE::cleanup();
}
}
最終的にプロジェクトツリーは、こんな風になった。