OpenGLES入门笔记:Rajawali学习(4)物体点击事件的实现

来源:互联网 发布:ed2k下载软件 编辑:程序博客网 时间:2024/04/29 20:30

背景

前面我们分别分析了Rajawali中场景的创建与物体的绘制,这篇文章我们将梳理一下点击事件的实现。这里我们参照general中的拖动例程,看看一个物体如何实现点击事件的捕获与响应。

实现流程

IObjectPicker

这个接口定义了Picker类的功能,Picker用于最终确定实际选择的物体到底是哪个。

//设置选择监听器public void setOnObjectPickedListener(OnObjectPickedListener objectPickedListener);//为Picker传入当前选择的坐标public void getObjectAt(float x, float y);

ObjectColorPicker

这个类实现了IObjectPicker接口,主要实现如下功能:

1.设置objectPickedListener,在回调接口中返回当前选中物体的引用

2.getObjectAt 获取点击的位置,用来传入坐标,计算那个物体被选择

3.registerObject 注册要被点击的物体,其中mObjectLookup列表中维
护要选择物体的列表

4.pickObject 静态方法,在Scene中调用,通过屏幕点击位置来获取被点击物体在mObjectLookup列表中索引,并通过objectPickedListener的onObjectPicked的参数返回这个物体的引用

现在我们具体看下这些功能的实现。

物体注册监听

响应触摸事件的物体列表

private final List<Object3D> mObjectLookup

将物体注册到Picker中

public void registerObject(Object3D object) {    if (!mObjectLookup.contains(object)) {        mObjectLookup.add(object);        //注意此处,mColorIndex是mObjectLookup中物体的索引值,被以某个颜色的方式记录在物体的一个叫做mPickingColor属性中        object.setPickingColor(mColorIndex);        ++mColorIndex;    }}

下面是setPickingColor方法的实现

public void setPickingColor(int colorIndex) {    mPickingIndex = colorIndex;    mPickingColor[RED] = Color.red(colorIndex) / 255f;    mPickingColor[GREEN] = Color.green(colorIndex) / 255f;    mPickingColor[BLUE] = Color.blue(colorIndex) / 255f;    mPickingColor[ALPHA] = Color.alpha(colorIndex) / 255f;}

绘制pickingMaterial

我们会发现Object3D类中还有一个变量叫做mColor。这个颜色是物体真实显示的颜色,那么,mPickingColor是什么呢?此处不免让人感觉疑惑,我们从字面意思来看,这个颜色值应该是与物体选择有关的,带着这个问题,我们继续追踪。

我们接着看Object3D类的renderColorPicking方法,mPickingColor的值最终在这里被使用。

...//pickingMaterial只初始化顶点和颜色pickingMaterial.useProgram();pickingMaterial.setVertices(mGeometry.getVertexBufferInfo());pickingMaterial.setColor(mPickingColor);pickingMaterial.applyParams();...//将绘制mMaterial的矩阵给了pickingMaterialpickingMaterial.setMVPMatrix(mMVPMatrix);pickingMaterial.setModelMatrix(mMMatrix);pickingMaterial.setModelViewMatrix(mMVMatrix);//绘制pickingMaterial     int bufferType = mGeometry.getIndexBufferInfo().bufferType == Geometry3D.BufferType.SHORT_BUFFER ? GLES20.GL_UNSIGNED_SHORT : GLES20.GL_UNSIGNED_INT;    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mGeometry.getIndexBufferInfo().bufferHandle);GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);

通过实验打印信息我们可以知道,这段代码会在触摸到物体的时候调用一次。也就是说,这个pickingMaterial只有在被点击的时候才会被绘制,而它的颜色就是就是我们上文中所说,那个由索引值生成的颜色

我们可以继续跟踪这个颜色的传递,可以发现所有相关的颜色都被传递到shader的一个uniform中。

muColorHandle = getUniformLocation(programHandle, DefaultShaderVar.U_COLOR);

我们可以打印一下这种情况下的的顶点和片元着色器,来看一下这个color值是怎么处理的。
VertexShader

    precision mediump float;    uniform mat4 uMVPMatrix;    uniform vec4 uColor;    uniform mat4 uModelMatrix;    uniform mat3 uNormalMatrix;    uniform mat4 uModelViewMatrix;    attribute vec3 aNormal;    attribute vec2 aTextureCoord;    attribute vec4 aPosition;    varying vec3 vNormal;    varying vec4 vColor;    varying vec2 vTextureCoord;    varying vec3 vEyeDir;    vec4 gColor;    vec4 gPosition;    vec3 gNormal;    vec2 gTextureCoord;    void main() {        gPosition = aPosition;        gNormal = aNormal;                                                                       gTextureCoord = aTextureCoord;        gColor = uColor;        gl_Position = uMVPMatrix * gPosition;        vNormal = normalize(uNormalMatrix * gNormal);                                                                      vTextureCoord = gTextureCoord;        vColor = gColor;        vEyeDir = vec3(uModelViewMatrix * gPosition);    }

fragment

    precision mediump float;    uniform float uColorInfluence;    varying vec3 vNormal;    varying vec4 vColor;    varying vec2 vTextureCoord;    varying vec3 vEyeDir;    float gShadowValue;    float gSpecularValue;    vec4 gColor;    vec3 gNormal;    vec2 gTextureCoord;    void main() {        gNormal = normalize(vNormal);        gTextureCoord = vTextureCoord;        gColor = uColorInfluence * vColor;        gShadowValue = 0.0;        gSpecularValue = 1.0;        gl_FragColor = gColor;    }

uColorInfluence 在FragmentShander中默认为1,所以,最终这个color是原封不动地给了gl_FragColor。这就是说,当我们点击的那一时刻,其实物体显示的是pickingMaterial,而其他时刻还是显示正常的Material。

获取点击物体

有了上面注册,我们接着整理点击事件时如何传递给物体的。

这里写图片描述

如上图所示,其实我们在上面分析的是Object3D的renderColorPicking方法,这个方法由Scene调用,在点击时,调用其中所有物体的renderColorPicking方法,也就是说给所有的物体换了张皮,而皮的颜色就是物体在Picker的物体列表中的索引值

下面我们就可以通过获取物体的颜色,来得到这个索引了。ObjectColorPicker的静态方法pickObject就是帮我们处理这个问题的。

public static void pickObject(ColorPickerInfo pickerInfo) {        final ObjectColorPicker picker = pickerInfo.getPicker();        OnObjectPickedListener listener = picker.mObjectPickedListener;        if (listener != null) {            final ByteBuffer pixelBuffer = ByteBuffer.allocateDirect(4);            pixelBuffer.order(ByteOrder.nativeOrder());            GLES20.glReadPixels(pickerInfo.getX(),                    picker.mRenderer.getViewportHeight() - pickerInfo.getY(),                    1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer);            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);            pixelBuffer.rewind();            //使用pickerInfo中的信息生成生成模型的索引值            final int r = pixelBuffer.get(0) & 0xff;            final int g = pixelBuffer.get(1) & 0xff;            final int b = pixelBuffer.get(2) & 0xff;            final int a = pixelBuffer.get(3) & 0xff;            final int index = Color.argb(a, r, g, b);            if (0 <= index && index < picker.mObjectLookup.size()) {                // Index may have holes due to unregistered objects                Object3D pickedObject = picker.mObjectLookup.get(index);                if (pickedObject != null) {                    //将被触摸的模型作为回掉接口的参数传递回去,我们在renderer中就可以得到物体的引用。                    listener.onObjectPicked(pickedObject);                    return;                }            }            listener.onNoObjectPicked();        }    }

可见,此处我们直接获取触摸点的颜色值,然后将其转换为数值并在列表中查找。如果列表中这个索引不为空,就将这个物体返回。

OnObjectPickedListener

回到我们的render中,由于需要进行触摸事件的监听,所以我们的render类实现了OnObjectPickedListener接口,这样直接在我们的render中实现public void onObjectPicked(@NonNull Object3D object)方法。这里参数就是我们当前所触摸的模型,所以我们可以从这里得到当前触摸模型的引用。

同时这里我们发现上面pickerInfo中的x,y其实是在getObjectAt方法中传入的,这里的x,y就是我们在touch事ACTION_DOWN时按下的触摸点。这里最终调用了下面方法,生成一个ColorPickerInfo,用来标识选中物体的信息。

mRenderer.getCurrentScene().requestColorPicking(new ColorPickerInfo(x, y, this));

这样,这个ColorPickerInfo被传入了Scene中。Scene的render方法调用doColorPicking(mPickerInfo);方法,而doColorPicking中调用ObjectColorPicker.pickObject(pickerInfo);,也就是我们文中讲到的,最终这里会通过回调接口返回那个被点击物体的引用。

而在接口的第三个方法stopMovingSelectedObject中,我们只需把被触摸模型的引用置为空即可。

实现模型随手指拖拽的效果

能获得模型的引用,我们只需修改模型的X,Y即可(一般情况手指平面上移动,不改变模型的Z坐标)。所以此处我们需要做的是将屏幕的X,Y坐标转换为模型在场景中的世界坐标。Demo中的处理方法如下,此处还需进一步理解。

    public void moveSelectedObject(float x, float y){            if (mSelectedObject == null)                return;            //将当前xy在近平面的屏幕坐标转换为世界坐标,mNearPos4为输出坐标            GLU.gluUnProject(x, getViewportHeight() - y, 0, mViewMatrix.getDoubleValues(), 0,                    mProjectionMatrix.getDoubleValues(), 0, mViewport, 0, mNearPos4, 0);            //将当前xy在远平面的屏幕坐标转换为世界坐标,mFarPos4为输出坐标            GLU.gluUnProject(x, getViewportHeight() - y, 1.f, mViewMatrix.getDoubleValues(), 0,                    mProjectionMatrix.getDoubleValues(), 0, mViewport, 0, mFarPos4, 0);            //将坐标同时除以w            mNearPos.setAll(mNearPos4[0] / mNearPos4[3], mNearPos4[1]                    / mNearPos4[3], mNearPos4[2] / mNearPos4[3]);            mFarPos.setAll(mFarPos4[0] / mFarPos4[3],                    mFarPos4[1] / mFarPos4[3], mFarPos4[2] / mFarPos4[3]);            //用归一化Z的值计算一个比率            double factor = (Math.abs(mSelectedObject.getZ()) + mNearPos.z)                    / (getCurrentCamera().getFarPlane() - getCurrentCamera()                    .getNearPlane());            mNewObjPos.setAll(mFarPos);            mNewObjPos.subtract(mNearPos);            mNewObjPos.multiply(factor);            mNewObjPos.add(mNearPos);            mSelectedObject.setX(mNewObjPos.x);            mSelectedObject.setY(mNewObjPos.y);        }

总结

上述流程比较复杂,在这里捋一下基本思路。首先,有一个Picker用作维护所有的要进行点击的物体列表。每个物体都有两张皮(两个Material,分别是mMaterial和pickingMatrial),其中mMaterial存储物体真实的材质,并且在一般情况下显示,pickingMatrial中存储一个颜色值,这个颜色值其实是这个物体在Picker中维护列表的索引值。当我们点击物体时,这一帧绘制其实绘制的是pickingMatrial这张皮,紧接着我们就把触摸点的颜色拿出来,还原成数字,并以此为索引在列表中寻找物体,如果找到,就把这个物体的引用返回,这样就获得了被点击的物体。

那么为什么要兜这么大的圈子来处理而不直接根据坐标判断触点呢?这里我猜想是这样的。对于一些简单的几何体我们很容易确定一个点是否包含在物体内,但是,脑补一个五角星,如何判断一个点是否在这种不规则图形内部呢?根据不同形状设置判断规则显然是不合适的。此处使用物体颜色作为索引就避开了这个问题,只要在触摸的那一帧进行颜色替换,就能直接拿到物体的索引。

2 0
原创粉丝点击