サンプルプログラム工場

AAKAKA Appで使われているプログラムのサンプルコードをそのまま公開!

サンプルプログラム工場 > android > AndroidのOpenGLで3D空間上のオブジェクトを指でドラッグするサンプル#tryDrag3dObject
Google Play AAKAKAxSOFTへ

AndroidのOpenGLで3D空間上のオブジェクトを指でドラッグするサンプル#tryDrag3dObject

実行ファイル(APK)やサンプル(zip)をダウンロードする
tryDrag3dObjectの実行イメージtryDrag3dObjectの実行イメージ

タッチパネルの指のスライドに合わせて、触っている3D空間上のオブジェクトを移動する。
指が移動するとタッチしている板が、指の移動にあわせてついてくるように。
直接指が触れているところの座標にオブジェクトを移動させようとすると、タッチしている場所と中心座標がずれているために変な位置に移動してしまう。
なので、前回のタッチが開始された位置からの動いた量を前回の位置に足し込む様にして移動する様にしている。

検索した事
 OpenGL マウスピッキング
 OpenGL レイピッキング
 プログラム マウスで移動 3Dオブジェクト
 opengl 深度バッファ

開発環境
 Eclipse IDE バージョン: 3.7 Indigo Service Release 2
 ターゲットプラットフォーム: 2.3.3
 API レベル: 10

package aakaka.junkcode.sample.trydrag3dobject;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.Matrix;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

// ////////////////////////////////////////////////////////////
// MainActivity
public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}


// ////////////////////////////////////////////////////////////
// GLSurfaceViewの拡張
class GLSurfaceViewExt extends GLSurfaceView {

	private GLRenderer mRenderer;
	public GLSurfaceViewExt(Context context, AttributeSet attrs) {
		super(context, attrs);

		this.initGLSurfaceView(context);
	}

	public GLSurfaceViewExt(Context context) {
		super(context);

		this.initGLSurfaceView(context);
	}

	public void initGLSurfaceView(Context contex) {
		this.setEGLContextClientVersion(2);
		// レンダラ―の設定
		this.mRenderer = new GLRenderer();
		this.setRenderer(this.mRenderer);
		this.setOnTouchListener(this.mRenderer);
	}
	

}




// ////////////////////////////////////////////////////////////
// レンダラー
class GLRenderer implements GLSurfaceView.Renderer, OnTouchListener {

	private float mAspect;	// アスペクト比
	private float mClip[] = { 0.1f, 100.f };	// クリップ面
	private Vec3[] mHitArea = {	// 当たりエリア
		new Vec3(-0.5f,  0.5f,  0.0f),
		new Vec3(0.5f,  0.5f,  0.0f),
		new Vec3(0.5f, -0.5f,  0.0f),
		new Vec3(-0.5f, -0.5f,  0.0f)
	};
	private Vec3 mPlanePos = new Vec3();	// 平面の中心座標(ドラッグ中の座標)
	private Vec3 mPlaneBasePos = new Vec3();	// 平面のベース座標(動かすときの基準座標)
	private Vec3 mDiffPos = new Vec3();
	private boolean mPlaneDrag;
	
	private Vec3 mTouch = new Vec3();	// 最新のタッチされたところ
	
	private Vec3 mDragStart = new Vec3();	// ドラッグ開始
	

	// マトリックを順番にスタックするもの
	private MatrixStack mModelViewStack = new MatrixStack();
	private MatrixStack mProjectionStack = new MatrixStack();
	
	private XYPlane mPlane;
	private Ray mRay = new Ray();

	// ////////////////////////////////////////////////////////////
	// コンストラクタ
	public GLRenderer() {
	}

	
	///////////////////////////////////////////////////////////////////////////
	/** 最初に呼ばれる */
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {

		
		this.mPlane = new XYPlane();
	}

	
	///////////////////////////////////////////////////////////////////////////
	// サーフェイスのサイズ変更時とかに呼ばれる 
	public void onSurfaceChanged(GL10 gl, int width, int height) {

		// ビューポートの再設定
		GLES20.glViewport(0, 0, width, height);

		// ビューを設定
		this.mRay.setView(0, 0, width, height);
		
		// アスペクト比
		this.mAspect = (float)width / (float)height;
	}

	// ////////////////////////////////////////////////////////////
	// 毎フレーム呼ばれるやつ
	public void onDrawFrame(GL10 gl) {

		float[] modelView = new float[16];
		float[] projection = new float[16];
		this.mProjectionStack.getMatrix(projection);
		this.mModelViewStack.getMatrix(modelView);

		// 画面に表示する ////////////////////////////////////////////////////////////
		
		// 画面をクリア
		GLES20.glClearColor(0.4f, 0.4f, 0.4f, 1.f);
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

		// 透視投影変換
		this.mProjectionStack.reset();
		this.mProjectionStack.applyPerspective(45.f, this.mAspect, this.mClip[0], this.mClip[1]);
		this.mRay.setClip(this.mClip[0], this.mClip[1]);
		this.mProjectionStack.push();
		
		// 視野変換
		this.mModelViewStack.reset();
		this.mModelViewStack.applyLookAt(
				0, 0, 5,
				0, 0, 0,
				0, 1, 0);
		this.mModelViewStack.push();

		// モデルを移動させる
		this.mModelViewStack.applyTranslate(this.mPlanePos.x, this.mPlanePos.y, this.mPlanePos.z);
		
		// MVP
		float[] mvp = new float[16];
		Matrix.multiplyMM(mvp, 0, projection, 0, modelView, 0);
		// 表示する
		this.mPlane.draw(mvp);

		
		// ////////////////////////////////////////////////////////////
		// ドラッグ処理
		
		// ドラッグ中
		if (this.mPlaneDrag == true) {

			// 今の場所と1個前の場所
			Vec3 curr = new Vec3(this.mTouch);
			Vec3 old = new Vec3(this.mDragStart);
			
			// ドラッグ開始の場所
			this.mRay.updateRay((int)old.x, (int)old.y, modelView, projection);
			// Planeの深度
			float depth = this.mRay.getDepth(mPlaneBasePos);
			Vec3 oldPos = this.mRay.getWorldPos(depth);

			// 今の場所
			this.mRay.updateRay((int)curr.x, (int)curr.y, modelView, projection);
			Vec3 currPos = this.mRay.getWorldPos(depth);
			
			// 新しい座標までのベクトルを求める
			this.mDiffPos = currPos.sub(oldPos);
			
			// ドラッグ開始座標から移動させる
			this.mPlanePos.set(mPlaneBasePos.sum(this.mDiffPos));
			
			this.mPlane.setColor(1, 0, 0, 1);
				
		} else {
			// 座標を決定する
			this.mPlaneBasePos.sum2(this.mDiffPos);
			this.mDiffPos.set(0, 0, 0);
			
			this.mPlane.setColor(0.5f, 0.5f, 0.5f, 1);
		}
	}


	
	
	// ////////////////////////////////////////////////////////////
	// タッチされたときに呼び出される
	public synchronized boolean onTouch(View v, MotionEvent event) {
		// アクションで処理わける
		switch (event.getAction()) {
		case MotionEvent.ACTION_MOVE:
			// 最新のタッチされた場所を更新
			this.mTouch.set(event.getX(), event.getY(), 0.0f);
			break;
		case MotionEvent.ACTION_DOWN:
			{
				// タッチ位置を今の位置にする
				this.mTouch.set(event.getX(), event.getY(), 0.0f);

				// 行列を計算用に取り出す
				float[] modelView = new float[16];
				float[] projection = new float[16];
				this.mProjectionStack.getMatrix(projection);
				this.mModelViewStack.getMatrix(modelView);
				
				this.mRay.updateRay((int)this.mTouch.x, (int)this.mTouch.y, modelView, projection);
				
				// 板を触ってる
				if (this.mRay.isHitRay(this.mHitArea) == true) {
					
					// タッチが開始された場所をセット
					mDragStart.set(this.mTouch);
					// ドラッグ開始
					this.mPlaneDrag = true;
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			{
				this.mPlaneDrag = false;
			}
			break;
		}
		
		return true;
	}
	
}







// ////////////////////////////////////////////////////////////
// RAYの当たり
class Ray {
	
	private int[] mViewport = { 0, 0, 0, 0 };	// ビューポート(左、上、幅、高さ)
	private int[] mScreenPos = { 0, 0 };	// スクリーン上の座標
	private float[] mStart = { 0, 0, 0, 0 };
	private float[] mEnd = { 0, 0, 0, 0 };
	private float[] mDepthClip = { 0.1f, 100.0f };	// クリップ面(nearとfar)
	private float[] mModelViewMatrix = new float[16];
	private float[] mProjectionMatrix = new float[16];
	private float[] mModelViewProjectionMatrix = new float[16];	// モデルビュープロジェクション
	
	
	// ////////////////////////////////////////////////////////////
	// コンストラクタ
	public Ray() {
		
	}
	
	// ////////////////////////////////////////////////////////////
	// ビューポート
	public void setView(int left, int top, int width, int height) {
		this.mViewport[0] = left;
		this.mViewport[1] = top;
		this.mViewport[2] = width;
		this.mViewport[3] = height;
	}

	// ////////////////////////////////////////////////////////////
	// クリップ面の設定
	public void setClip(float near, float far) {
		this.mDepthClip[0] = near;
		this.mDepthClip[1] = far;
	}
	
	// ////////////////////////////////////////////////////////////
	// Rayワールド上の開始位置と終了位置を更新する
	public void updateRay(int x, int y, final float[] modelView, final float[] projection) {

		// 行列をコピーしておく
		System.arraycopy(modelView, 0, this.mModelViewMatrix, 0, 16);
		System.arraycopy(projection, 0, this.mProjectionMatrix, 0, 16);
		Matrix.multiplyMM(this.mModelViewProjectionMatrix, 0, 
				this.mProjectionMatrix, 0, this.mModelViewMatrix, 0);
		
		this.mScreenPos[0] = x;
		this.mScreenPos[1] = y;
		
		int[] viewport = this.mViewport;
		
		float[] startPos = this.mStart;
		float[] endPos = this.mEnd;

		// 近い方の面の場合はこの位置
		GLU.gluUnProject(x, viewport[3] - y, 0.0f,
				modelView, 0, projection, 0,
				viewport, 0, startPos, 0);
		startPos[0] /= startPos[3];
		startPos[1] /= startPos[3];
		startPos[2] /= startPos[3];
		
		// 遠い方の面の場合はこの位置
		GLU.gluUnProject(x, viewport[3] - y, 1.0f,
				modelView, 0, projection, 0,
				viewport, 0, endPos, 0);
		endPos[0] /= endPos[3];
		endPos[1] /= endPos[3];
		endPos[2] /= endPos[3];
		
	}
	
	
	public Vec3 getWorldPos(float depth) {
		float[] startPos = this.mStart;
		float[] endPos = this.mEnd;

		return new Vec3(
				this.mStart[0] + ((endPos[0] - startPos[0]) * depth),
				startPos[1] + ((endPos[1] - startPos[1]) * depth),
				startPos[2] + ((endPos[2] - startPos[2]) * depth)
				);
	}
	
	// ////////////////////////////////////////////////////////////
	// RayStartからRayEndの間に指定の矩形が当たっているか
	public boolean isHitRay(Vec3[] posList) {
		Vec3 rayStart = new Vec3(this.mStart);
		Vec3 rayDir = new Vec3(this.mEnd);
		rayDir.sub2(rayStart);
		rayDir.normalize();

		// 法線の計算
		Vec3 t0 = posList[1].sub(posList[0]);
		Vec3 t1 = posList[2].sub(posList[0]);
		Vec3 normal = t1.cross(t0);
		normal.normalize();

		// 線分判定
		Vec3 xp = posList[0].sub(rayStart);
		float xpn = xp.dot(normal);
		float vn = rayDir.dot(normal);
		
		// かリングと発散を外す(なんのこと?)
		if (-0.00001f <= vn) {
			return false;
		}
		
		// 当たってる場所までの長さ
		float t = xpn / vn;
		
		// 後ろ向きのRAYは無視する
		if (t < 0.0f) {
			return false;
		}
		
		// 三角形の平面へ射影(当たってる当たってる場所までのベクトルだわね、これ)
		Vec3 p = rayStart.sum(rayDir.scale(t));
		
		// ポイントが渡された座標の中にあるかどうか
		for (int i = 1, len = posList.length; i <= len; i++) {
			Vec3 d0 = p.sub(posList[i - 1]);
			Vec3 d1 = posList[i % len].sub(posList[i - 1]);
			Vec3 c = d0.cross(d1);
			if (c.dot(normal) < 0.0f)
				return false;
		}
		
		return true;
	}
	
	
	
	// ////////////////////////////////////////////////////////////
	// 深度を求める
	public float getDepth(Vec3 pos) {
		float[] res = { 0, 0, 0, 0 };
		Matrix.multiplyMV(res, 0, this.mModelViewProjectionMatrix, 0, pos.toF4(), 0);
		return res[2] / (this.mDepthClip[1] - this.mDepthClip[0]);
	}
}













//////////////////////////////////////////////////////////////
//XY平面
class XYPlane {
	
	// 頂点内容のインデックス をこちらから指定する
	public static final int ATTRIBUTE_POSITION_LOCATION = 0;
	public static final int ATTRIBUTE_COLOR_LOCATION = 1;
	public static final String ATTRIBUTE_POSITION = "a_pos";
	public static final String ATTRIBUTE_COLOR = "a_color";
	
	public static final String UNIFORM_MVP_MATRIX = "u_mvpMatrix";	// モデルビュープロジェクション
	public static final String UNIFORM_COLOR = "u_color";
	public static final String VARYING_COLOR = "v_color";
	
	// 頂点カラー 付き頂点シェーダーのコンパイル
	private static final String VERTEX_CODE =
			"uniform mat4 u_mvpMatrix;" +
			"uniform vec4 u_color;" + 
			"attribute vec4 a_pos;" +
			"varying vec4 v_color;" +
			"void main(){"+       
			"   gl_Position = u_mvpMatrix * a_pos;" +
			"	v_color = u_color;" +
			"}";

	// 頂点カラー 付きフラグメントシェーダーのコンパイル
	private static final String FRAGMENT_CODE =
			"precision mediump float;"+
			"varying vec4 v_color;" +
			"void main(){"+
			"    gl_FragColor = v_color;" + 
			"}";
	
	// 描画するためのもの
	private int mVertexShaderID;	// 頂点シェーダーID
	private int mFragmentShaderID;	// フラグメントシェーダーID
	private int mProgramID; // プログラムオブジェクトID

	// シェーダーに値を送るためのハンドル
	private int mLocMVPMatrix;
	private int mLocColor;

	// VBO の管理番号
	private int mVertexBufferID; // 頂点バッファ
	
	private float[] mFaceColor = { 1, 1, 1, 1 };


	
	// ////////////////////////////////////////////////////////////
	// コンストラクタ
	public XYPlane() {
		
		// 頂点配列を有効にする
		GLES20.glEnableVertexAttribArray(ATTRIBUTE_POSITION_LOCATION);
		
		// シェーダーを初期化する
		this.initShader();

		// VBO を初期化する
		this.initVBO();
	}
	
	// ////////////////////////////////////////////////////////////
	// 表示する
	public void draw(final float[] mvp) {
		// VBO での描画
		// 頂点バッファのセット
		GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, this.mVertexBufferID);
		
		// 頂点内容のインデックス で、各値の場所を指定する
		int vertexSize = 4 * 3;	// 4(float)バイトがの数
		GLES20.glVertexAttribPointer(ATTRIBUTE_POSITION_LOCATION, 3, GLES20.GL_FLOAT, false, vertexSize, 0);

		// シェーダーにモデルビュー行列を送信
		GLES20.glUniformMatrix4fv(this.mLocMVPMatrix, 1, false, mvp, 0);
		
		// シェーダーにモデルビュー行列を送信
		GLES20.glUniform4fv(this.mLocColor, 1, this.mFaceColor, 0);
		
		
		// 面を描く
		GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
	}
	
	// ////////////////////////////////////////////////////////////
	// 色をセットする
	public void setColor(float r, float g, float b, float a) {
		this.mFaceColor[0] = r;
		this.mFaceColor[1] = g;
		this.mFaceColor[2] = b;
		this.mFaceColor[3] = a;
	}
	
	
	// /////////////////////////////////////////////////////////////////////////
	// VBOを登録する
	private void initVBO() {

		// 頂点座標 + 頂点カラー
		// 頂点バッファから VBO を作る
		float[] vertices = {
				// 座標(x, y, z) COLOR
				-0.5f,  0.5f,  0.0f,
				-0.5f, -0.5f,  0.0f,
				 0.5f,  0.5f,  0.0f,
				 0.5f, -0.5f,  0.0f,
		};
		FloatBuffer vertexBuffer = this.makeFloatBuffer(vertices);
		// 頂点データ(座標、法線、色、テクスチャ座標)には
		// GL_ARRAY_BUFFERを指定する
		this.mVertexBufferID = this.makeVBO(
				vertexBuffer, 4, GLES20.GL_ARRAY_BUFFER);
		
	}

	// /////////////////////////////////////////////////////////////////////////
	// 各バッファから VBO へ変換する
	private int makeVBO(Buffer buffer, int size, int target) {

		int[] hardwareIDContainer= { -1 };

		// ハードウェア側の準備
		GLES20.glGenBuffers(1, hardwareIDContainer, 0);
		GLES20.glBindBuffer(target, 
				hardwareIDContainer[0]);
		GLES20.glBufferData(target,
				buffer.capacity() * size, buffer, GLES20.GL_STATIC_DRAW);

		return hardwareIDContainer[0];
	}

	// /////////////////////////////////////////////////////////////////////////
	// シェーダーを初期化する
	private void initShader() {
		
		// シェーダーのコンパイル
		this.mVertexShaderID = this.compileShader(
				GLES20.GL_VERTEX_SHADER, VERTEX_CODE);

		this.mFragmentShaderID = this.compileShader(
				GLES20.GL_FRAGMENT_SHADER, FRAGMENT_CODE);

		// プログラムオブジェクトを作る
		this.mProgramID = GLES20.glCreateProgram();
		GLES20.glAttachShader(this.mProgramID, this.mVertexShaderID);
		GLES20.glAttachShader(this.mProgramID, this.mFragmentShaderID);

		// 頂点内容のインデックス をシェーダー変数と関連付ける
		GLES20.glBindAttribLocation(this.mProgramID, ATTRIBUTE_POSITION_LOCATION, ATTRIBUTE_POSITION);


		GLES20.glLinkProgram(this.mProgramID);

		// シェーダーに値を送るためのハンドルを取り出す
		this.mLocMVPMatrix = GLES20.glGetUniformLocation(this.mProgramID, UNIFORM_MVP_MATRIX);
		
		// シェーダーに値を送るためのハンドルを取り出す
		this.mLocColor = GLES20.glGetUniformLocation(this.mProgramID, UNIFORM_COLOR);

		// プログラムオブジェクトを使い始める
		GLES20.glUseProgram(this.mProgramID);
	}
	
	
	///////////////////////////////////////////////////////////////////////////
	// FloatBufferを作って値をセットする
	private FloatBuffer makeFloatBuffer(float[] values) {
		// バッファを作る
		FloatBuffer fb = ByteBuffer.allocateDirect(values.length * 4)
				.order(ByteOrder.nativeOrder())
				.asFloatBuffer();
		// 作ったバッファに値をセットしておく
		fb.put(values)
		.position(0);
		return fb;
	}

	///////////////////////////////////////////////////////////////////////////
	// シェーダーのソースコードをコンパイルする
	private int compileShader(int type, String code) {
		final int shaderId = GLES20.glCreateShader(type);
		if (shaderId == 0) {
			// シェーダーの領域確保に失敗した
			Log.d("compileShader", "領域確保に失敗");
			return -1;
		}
		// シェーダーをコンパイル
		GLES20.glShaderSource(shaderId, code);
		GLES20.glCompileShader(shaderId);

		// コンパイルが成功したか調べる
		int[] res = new int[1];
		GLES20.glGetShaderiv(shaderId, GLES20.GL_COMPILE_STATUS, res, 0);
		if (res[0] == 0) {
			// 失敗してる
			Log.d("compileShader", GLES20.glGetShaderInfoLog(shaderId));
			return -1;
		}
		return shaderId;
	}
}



サンプルプロジェクトをダウンロード APKファイルをダウンロード

, , , , , , , ,

Androidで音を鳴らすサンプル#trySound00 AndroidのOpenGLでCOLLADA(dae)ファイルを読み込んで表示するサンプル#try...

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>