Android Open Source - GoogleBody Render






From Project

Back to project page GoogleBody.

License

The source code is released under:

Apache License

If you think the Android project GoogleBody listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

// Copyright 2011 Google Inc. All Rights Reserved.
///*from w  w  w  . ja  v  a 2  s  . c o m*/
// 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.
package com.google.android.apps.body;

import com.google.android.apps.body.tdl.Programs;
import com.google.android.apps.body.tdl.TdlMath;
import com.google.android.apps.body.tdl.Textures;

import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.ETC1Util.ETC1Texture;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Responsible for rendering the GL display. Lives on the GL thread.
 */
public class Render implements Layers.Listener, LayersLoader.Callback {

    private Navigate mNavigate;
    private int mClientWidth, mClientHeight;

    public Render(Navigate navigate) {
        this.mNavigate = navigate;
    }

    void setClientSize(int width, int height) {
        mClientWidth = width;
        mClientHeight = height;
        mNavigate.setAspect(width / (float)height);
    }

    private List<Interpolant> layerOpacityInterpolants = new ArrayList<Interpolant>();

    // The layers should be added from inside out
    @SuppressWarnings("serial")
    static final Map<Integer, Integer> layerInfo = new LinkedHashMap<Integer, Integer>() {{
        put(Layers.NERVOUS, R.raw.f_nervous);
        put(Layers.CIRCULATORY, R.raw.f_circulatory);
        put(Layers.ORGANS, R.raw.f_organs_no_breasts);
        put(Layers.CONNECTIVE, R.raw.f_connective);
        put(Layers.SKELETON, R.raw.f_skeleton);
        put(Layers.MUSCLE, R.raw.f_muscles);
        put(Layers.SKIN, R.raw.f_skin_with_breasts);
    }};

    float selectionColorScale = 1;

    private static final String VERTEX_SHADER_SELECTION =
        "precision highp float; \n" +
        "uniform mat4 worldViewProjection; \n" +
        "uniform float colorScale; \n" +
        "attribute vec3 position; \n" +
        "attribute float colorIndex; \n" +
        "varying vec4 pColor;\n" +
        "void main() { \n" +
        "  float scaledColor = colorIndex * colorScale; \n" +
        "  float redColor = floor(scaledColor / (256.0 * 256.0)); \n" +
        "  float greenColor = floor((scaledColor - redColor * 256.0 * 256.0) / 256.0); \n" +
        "  float blueColor = (scaledColor - greenColor * 256.0 - redColor * 256.0 * 256.0); \n" +
        "  pColor = vec4(redColor / 255.0, greenColor / 255.0, blueColor / 255.0, 1.0); \n " +
        "  gl_Position = worldViewProjection * vec4(position, 64.0); \n" +
        "}";

    private static final String FRAGMENT_SHADER_SELECTION =
        "precision highp float; \n" +
        "varying vec4 pColor; \n" +
        "void main() { \n" +
        "  gl_FragColor = pColor; \n" +
        "}";

    private static final String VERTEX_SHADER_LIGHT =
        "precision mediump float; \n" +
        "uniform mat4 worldViewProjection; \n" +
        "uniform mat4 worldView; \n" +
        "attribute vec3 position; \n" +
        "attribute vec2 texCoord; \n" +
        "attribute vec3 normal; \n" +
        "varying vec3 pNormal; \n" +
        "varying vec2 pTexCoord; \n" +
        "void main() { \n" +
        "  pNormal = (worldView * vec4(normal, 0.0)).xyz; \n" +
        "  pTexCoord = texCoord / 512.0; \n" +
        "  gl_Position = worldViewProjection * vec4(position, 64.0); \n" +
        "}";

    // This is slightly simpler than the web version for performanc reasons,
    // for example no specular highlighting is done.
    // It also uses |precision mediump|. |lowp| made the textures very blocky on
    // the tablet, and |mediump| looks better and is the same speed on tablet.
    // On my Nexus S, |lowp| looked as good as |mediump| and was faster though.
    // On the tablet, it's ~1ms/frame faster to have the whole shader |mediump|
    // (as opposed to making just |pTextCoord| mediump).
    private static final String FRAGMENT_SHADER_LIGHT =
        "precision mediump float; \n" +
        "uniform float opacity; \n" +
        "uniform sampler2D textureSampler; \n" +
        "varying vec3 pNormal; \n" +
        "varying vec2 pTexCoord; \n" +
        "void main() { \n" +
        "  vec3 lightVector = vec3(0.3, 0.3, 0.9); \n" +
        "  vec3 normal = normalize(pNormal); \n" +
        "  float light = 0.1; \n" +
        "  light += 0.9*max(dot(normal, lightVector), 0.0); \n" +
        "  vec3 textureColor = texture2D(textureSampler, pTexCoord).rgb; \n" +
        "  gl_FragColor = vec4(light*textureColor*opacity, opacity); \n" +  // Real thing
        "}";

    private int mShaderWithLights;
    private int mWorldViewProjectionLoc;
    private int mWorldViewLoc;
    private int mOpacityLoc;
    private int mTextureSamplerLoc;
    private float[] mWvpMatrix = new float[16];
    private float[] mViewMatrix = new float[16];

    private Layer[] mLayers = new Layer[Layers.NUM_LAYERS];

    private int mSelectionShader;
    private int mSelectionWorldViewProjectionLoc;
    private int mColorScaleLoc;
    private Map<Integer, Draw> mSelectionColorMap;
    private int mMaxColorIndex = 1;
    private ByteBuffer mSelectionSurfaceBuffer;

    private int createShortBuffer(int target, ShortBuffer buffer) {
        int[] buffers = { 0 };
        GLES20.glGenBuffers(1, buffers, 0);
        GLES20.glBindBuffer(target, buffers[0]);
        GLES20.glBufferData(target, buffer.capacity() * 2, buffer, GLES20.GL_STATIC_DRAW);
        return buffers[0];
    }

    private Layer initLayer(int info) {
        Layer layer = new Layer(info);
        float initialOpacity = 0;
        if (info == Layers.SKIN)
            initialOpacity = 1;
        layerOpacityInterpolants.add(layer.opacity = new Interpolant(initialOpacity));
        layer.renderOpacity = initialOpacity;
        return layer;
    }

    private void loadLayers(Context context, final BodyActivity ui) {
        for (Integer info : layerInfo.keySet()) {
            if (mLayers[info] == null) mLayers[info] = initLayer(info);
        }

        final Map<Integer, Integer> layerResources = new LinkedHashMap<Integer, Integer>();
        // layerInfo is inside out, but we want to load the skin first.
        List<Integer> layers = new ArrayList<Integer>(layerInfo.keySet());
        Collections.reverse(layers);
        for (int i : layers)
            layerResources.put(i, layerInfo.get(i));

        ui.getHandler().post(new Runnable() {
                @Override
                public void run() {
                    ui.load(this, layerResources);
                }});
    }

    private void prepareDraw(Layer layer, float opacity) {
        float renderOpacity = opacity;
        renderOpacity = (renderOpacity > 1 ? 1 : renderOpacity);
        renderOpacity = (renderOpacity < 0 ? 0 : renderOpacity);

        if (renderOpacity != layer.renderOpacity) {
            layer.renderOpacity = renderOpacity;
        }

        GLES20.glUniform1f(mOpacityLoc, layer.renderOpacity);

        GLES20.glUniform1i(mTextureSamplerLoc, 0);  // Not in js version.    
    }

    private void drawOneGeometryOnly(Layer layer, String geometry) {
        // Only drawing the selected group.
        for (DrawGroup drawGroup : layer.drawGroups) {
            for (Render.Draw draw : drawGroup.draws) {
                if (draw.geometry.equals(geometry)) {
                    drawElements(
                            drawGroup.vertexBuffer,
                            drawGroup.indexBuffer,
                            drawGroup.diffuseTexture,
                            draw.offset,
                            draw.count);
                }
            }
        }
    }

    private void drawLayer(Layer layer, float opacity) {
        if (layer.drawGroups == null) {
            return;
        }

        prepareDraw(layer, opacity);

        for (DrawGroup drawGroup : layer.drawGroups) {
            drawElements(
                    drawGroup.vertexBuffer,
                    drawGroup.indexBuffer,
                    drawGroup.diffuseTexture,
                    0,
                    drawGroup.numIndices);
        }
    }

    private void drawElements(int vertexBuffer, int indexBuffer,
            int diffuseTexture, int offset, int numIndices) {
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer);
        GLES20.glVertexAttribPointer(0, 3, GLES20.GL_SHORT, false, 2 * (3 + 3 + 2), 0);

        GLES20.glVertexAttribPointer(1, 3, GLES20.GL_SHORT, false, 2 * (3 + 3 + 2), 2 * 3);

        GLES20.glVertexAttribPointer(2, 2, GLES20.GL_SHORT, false, 2 * (3 + 3 + 2), 2 * (3 + 3));
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, diffuseTexture);
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, numIndices, GLES20.GL_UNSIGNED_SHORT, offset * 2);    
    }

    private void updateMatrices(int w, int h) {
        Navigate.Camera camera = mNavigate.getCamera();
        float[] eyePosition = camera.eye;
        float[] targetPosition = camera.target;
        float[] upPosition = camera.up;

        Matrix.setLookAtM(mViewMatrix, 0,
                eyePosition[0], eyePosition[1], eyePosition[2],
                targetPosition[0], targetPosition[1], targetPosition[2],
                upPosition[0], upPosition[1], upPosition[2]);

        // Mobile devices have only 16 bits of depth buffer, so limit the depth
        // range. This is a heuristic which works pretty well except if you
        // look at the model from above, in which case the skull gets clipped
        // failry early. But this almost looks like a feature.
        // TODO(thakis): Make this smarter once we have toplevel bounding boxes.
        float tz = mViewMatrix[3 * 4 + 2];
        tz = -tz;  // tz is always negative, make it positive (max 250).

        final float BODY_HEIGHT = 400;  // TODO: tweak?
        final float F = 1/5.f;

        float[] projectionMatrix = new float[16];
        float aspect = w / (float)h;
        TdlMath.perspective(
                projectionMatrix,
                (float)Math.toRadians(camera.fov),
                aspect,
                Math.max(10.f, tz - BODY_HEIGHT * F),
                tz + (1 - F)*BODY_HEIGHT);

        // The world matrix is always the identity.
        Matrix.multiplyMM(mWvpMatrix, 0, projectionMatrix, 0, mViewMatrix, 0);
    }

    private static class OffscreenSurface {
        int framebuffer;
        int renderbuffer;
        int colorTexture;
    }

    OffscreenSurface createOffscreenSurface(int width, int height) {
        OffscreenSurface result = new OffscreenSurface();

        int[] framebuffers = { 0 };
        GLES20.glGenFramebuffers(1, framebuffers, 0);
        result.framebuffer = framebuffers[0];
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, result.framebuffer);

        // Color buffer.
        int[] textures = { 0 };
        GLES20.glGenTextures(1, textures, 0);
        result.colorTexture = textures[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, result.colorTexture);
        GLES20.glTexParameteri(
                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(
                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(
                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(
                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexImage2D(
                GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

        // Depth buffer.
        int[] renderbuffers = { 0 };
        GLES20.glGenRenderbuffers(1, renderbuffers, 0);
        result.renderbuffer = renderbuffers[0];
        GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, result.renderbuffer);
        GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
                width, height);

        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, result.colorTexture, 0);
        GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
                GLES20.GL_RENDERBUFFER,
                result.renderbuffer);

        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
            Log.w("Body", "Incomplete framebuffer");
            result.framebuffer = 0;
        }
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        return result;
    }

    void drawBodyForSelection(int x, int y, int fboWidth, int fboHeight) {
        updateMatrices(mClientWidth, mClientHeight);

        // Zoom in on the picking rectangle.
        float[] pickingMatrix = new float[16];
        TdlMath.pickMatrix(
                pickingMatrix, x, y, fboWidth, fboHeight,
                new float[]{0, 0, mClientWidth, mClientHeight});
        Matrix.multiplyMM(mWvpMatrix, 0, pickingMatrix, 0, mWvpMatrix.clone(), 0);

        GLES20.glUseProgram(mSelectionShader);
        GLES20.glUniformMatrix4fv(mSelectionWorldViewProjectionLoc, 1, false, mWvpMatrix, 0);
        selectionColorScale = (float)Math.floor((256*256*256-1) / (float)mMaxColorIndex );
        GLES20.glUniform1f(mColorScaleLoc, selectionColorScale);

        GLES20.glViewport(0, 0, fboWidth, fboHeight);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glClearColor(0, 0, 0, 1);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        GLES20.glDisable(GLES20.GL_BLEND);

        GLES20.glEnableVertexAttribArray(0);
        GLES20.glEnableVertexAttribArray(1);
        GLES20.glDisableVertexAttribArray(2);

        for (int info : layerInfo.keySet()) {
            Layer layer = mLayers[info];

            if (layer.drawGroups == null)
                continue;

            if (layer.renderOpacity < 0.5 && !layer.isVisibleTarget)
                continue;

            for (DrawGroup drawGroup : layer.drawGroups) {
                GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, drawGroup.vertexBuffer);
                GLES20.glVertexAttribPointer(0, 3, GLES20.GL_SHORT, false, 2 * (3 + 3 + 2), 0);
                GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, drawGroup.colorBuffer);
                GLES20.glVertexAttribPointer(1, 1, GLES20.GL_UNSIGNED_SHORT, false, 2 * 1, 0);
                GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, drawGroup.indexBuffer);
                GLES20.glDrawElements(
                        GLES20.GL_TRIANGLES, drawGroup.numIndices, GLES20.GL_UNSIGNED_SHORT, 0);
            }
        }

        if (mSelectionSurfaceBuffer != null) {
            GLES20.glReadPixels(
                    0, 0, fboWidth, fboHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
                    mSelectionSurfaceBuffer);
        }
    }

    private int getPixel(int sx, int sy, int width, int height, ByteBuffer data) {
        if (sx < 0 || sx >= width || sy < 0 || sy >= height) return 0;

        int startByte = (sy * width + sx) * 4;
        short red = (short) (((short) data.get(startByte + 0)) & 0xff);
        short green = (short) (((short) data.get(startByte + 1)) & 0xff);
        short blue = (short) (((short) data.get(startByte + 2)) & 0xff);
        return blue + green * 256 + red * 256 * 256;
    }

    // It's hard to select thin entites when tapping. Search in concentric
    // growing rectangles instead of checking just a single pixel to
    // rectify this.
    private int findPixelInRect(
            int sx, int sy, int windowSize, int width, int height, ByteBuffer data) {
        // Check center.
        int value = getPixel(sx, sy, width, height, data);
        if (value != 0) return value;

        // Walk growing rectangle edges.
        for (int d = 1; d <= windowSize / 2; ++d) {
            for (int y = sy - d; y <= sy + d; ++y) {
                if (y < 0) continue;
                if (y >= height) break;

                value = getPixel(sx - d, y, width, height, data);
                if (value != 0) return value;
                value = getPixel(sx + d, y, width, height, data);
                if (value != 0) return value;
            }
            for (int x = sx - d + 1; x <= sx + d - 1; ++x) {
                if (x < 0) continue;
                if (x >= width) break;

                value = getPixel(x, sy - d, width, height, data);
                if (value != 0) return value;
                value = getPixel(x, sy + d, width, height, data);
                if (value != 0) return value;
            }
        }
        return 0;
    }

    public String getEntityAtCoord(int x, int y) {
        if (x < 0 || x > mClientWidth || y < 0 || y > mClientHeight)
            return "";

        final int kSelectionRectWidth = 20;

        // Render at a much smaller resolution (only 20x20 pixel around touch point).
        int fboWidth = kSelectionRectWidth;
        int fboHeight = kSelectionRectWidth;

        OffscreenSurface selection = createOffscreenSurface(fboWidth, fboHeight);
        if (selection == null) {
            Log.w("Body", "Failed to create framebuffer");
            return "";
        }
        int selectionSurfaceSize = fboWidth * fboHeight * 4;
        mSelectionSurfaceBuffer = ByteBuffer.allocateDirect(selectionSurfaceSize);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, selection.framebuffer);

        drawBodyForSelection(x, mClientHeight - 1 - y, fboWidth, fboHeight);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        int sx = kSelectionRectWidth / 2, sy = kSelectionRectWidth / 2;
        int value = findPixelInRect(
                sx, sy, kSelectionRectWidth, fboWidth, fboHeight, mSelectionSurfaceBuffer);

        // Clear stuff.
        mSelectionSurfaceBuffer = null;
        int[] framebuffers = { selection.framebuffer };
        GLES20.glDeleteFramebuffers(1, framebuffers, 0);
        int[] renderbuffers = { selection.renderbuffer };
        GLES20.glDeleteRenderbuffers(1, renderbuffers, 0);
        int[] textures = { selection.colorTexture };
        GLES20.glDeleteTextures(1, textures, 0);

        value = (int)Math.floor(value / selectionColorScale);
        if (value != 0 && mSelectionColorMap.containsKey(value)) {
            return mSelectionColorMap.get(value).geometry;
        } else {
            return "";
        }
    }

    void drawBody() {
        Interpolant.tweenAll(layerOpacityInterpolants);

        updateMatrices(mClientWidth, mClientHeight);

        GLES20.glUseProgram(mShaderWithLights);

        GLES20.glUniformMatrix4fv(mWorldViewProjectionLoc, 1, false, mWvpMatrix, 0);
        GLES20.glUniformMatrix4fv(mWorldViewLoc, 1, false, mViewMatrix, 0);

        GLES20.glActiveTexture(0);
        GLES20.glEnableVertexAttribArray(0);
        GLES20.glEnableVertexAttribArray(1);
        GLES20.glEnableVertexAttribArray(2);

        GLES20.glViewport(0, 0, mClientWidth, mClientHeight);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glClearColor(1, 1, 1, 1);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        Layer skinLayer = mLayers[Layers.SKIN];
        if (Select.haveSelectedEntity()) {
            String targetEntity = Select.getEntity();
            List<Integer> targetLayers = new ArrayList<Integer>();
            List<Integer> otherLayers = new ArrayList<Integer>();
            for (int info : layerInfo.keySet()) {
                mLayers[info].isVisibleTarget = false;
                int targetLayerIndex = Select.getLayer();

                // Skeleton and connective layers are conjoined.
                if (info == targetLayerIndex||
                        (targetLayerIndex == Layers.SKELETON && info == Layers.CONNECTIVE ||
                         targetLayerIndex == Layers.CONNECTIVE && info == Layers.SKELETON)) {
                    targetLayers.add(info);
                } else {
                    otherLayers.add(info);
                }
            }

            // Switch to transparenting layers.
            GLES20.glEnable(GLES20.GL_BLEND);
            GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

            // We draw the layers in the same order they draw when there's
            // nothing selected to avoid snapping when switching between selection
            // rendering and regular rendering.
            for (int info : layerInfo.keySet()) {
                Layer layer = mLayers[info];
                boolean isTarget = false;

                // Check if we've come across one of the target layers.
                for (int targetLayerIndex : targetLayers) {
                    Layer targetLayer = mLayers[targetLayerIndex];

                    if (layer == targetLayer && layer.drawGroups != null) {
                        isTarget = true;

                        // First render the selected entity...
                        float targetOpacity = targetLayer.opacity.getPresent();
                        targetOpacity += (1 - targetOpacity) * Select.getSelectedEntityOpacity();
                        prepareDraw(layer, targetOpacity);
                        drawOneGeometryOnly(targetLayer, targetEntity);

                        // ... and then its layer
                        float targetLayerOpacity =
                                targetLayer.opacity.getPresent() * Select.getSelectedLayerOpacity();
                        if (targetLayerOpacity > 0.05) {
                            prepareDraw(layer, targetLayerOpacity);
                            drawLayer(targetLayer, targetLayerOpacity);
                            targetLayer.isVisibleTarget = true;
                        } else {
                            targetLayer.renderOpacity = 0;
                        }
                    }
                }

                if (!isTarget)
                    layer.renderOpacity = 0;
            }
            GLES20.glDisable(GLES20.GL_BLEND);

        } else if (skinLayer.opacity.getPresent() >= 0.95) {
            drawLayer(skinLayer, skinLayer.opacity.getPresent());
        } else {
            List<Layer> opaqueLayers = new ArrayList<Layer>();
            List<Layer> transparentLayers = new ArrayList<Layer>();
            for (Integer info : layerInfo.keySet()) {
                Layer layer = mLayers[info];
                layer.isVisibleTarget = false;

                if (layer.opacity.getPresent() >= 0.95) {
                    opaqueLayers.add(layer);
                } else if (layer.opacity.getPresent() > 0.05) {
                    transparentLayers.add(layer);
                } else {
                    layer.renderOpacity = 0;
                }
            }

            int lastLayer = 0;
            GLES20.glDisable(GLES20.GL_BLEND);
            for (int ii = opaqueLayers.size() - 1; ii >= 0; --ii) {
                Layer layer = opaqueLayers.get(ii);
                drawLayer(layer, 1.0f);
                lastLayer = layer.type;
            }

            if (transparentLayers.size() > 0) {
                GLES20.glEnable(GLES20.GL_BLEND);
                // This loop will run at most twice.
                for (int ii = 0; ii < transparentLayers.size(); ++ii) {
                    Layer layer = transparentLayers.get(ii);

                    // It looks weird if the inner layer fades out and z-blocks the outer layer.
                    // Always draw the outer layer on top.
                    if (ii > 0) {
                        boolean connectiveAndSkeleton =
                                lastLayer == Layers.CONNECTIVE && layer.type == Layers.SKELETON;

                        // connective and skeleton are rendered as one unit
                        // TODO: put them in a single layer
                        if (!connectiveAndSkeleton)
                            GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT);
                    }

                    drawLayer(layer, layer.opacity.getPresent());
                    lastLayer = layer.type;
                }
                GLES20.glDisable(GLES20.GL_BLEND);
            }
        }
    }


    public float[] viewportCoords(float[] p) {
        float[] cameraMatrix = mWvpMatrix;
        float x =
            cameraMatrix[0]  * p[0] +
            cameraMatrix[4]  * p[1] +
            cameraMatrix[8]  * p[2] +
            cameraMatrix[12];
        float y =
            cameraMatrix[1]  * p[0] +
            cameraMatrix[5]  * p[1] +
            cameraMatrix[9]  * p[2] +
            cameraMatrix[13];
        float w =
            cameraMatrix[3]  * p[0] +
            cameraMatrix[7]  * p[2] +
            cameraMatrix[11] * p[2] +
            cameraMatrix[15];

        x = x / w;
        y = y / w;

        x = (x + 1) * mClientWidth / 2;
        y = (2 - (y + 1)) * mClientHeight / 2;

        float[] result = { x, y };
        return result;
    }

    public void initialize(Context context, BodyActivity ui) {
        Layers.addView(this);

        // Selection shader
        mSelectionShader = Programs.loadProgram(VERTEX_SHADER_SELECTION, FRAGMENT_SHADER_SELECTION);
        GLES20.glBindAttribLocation(mSelectionShader, 0, "position");
        GLES20.glBindAttribLocation(mSelectionShader, 1, "colorIndex");
        GLES20.glLinkProgram(mSelectionShader);
        mSelectionWorldViewProjectionLoc =
                GLES20.glGetUniformLocation(mSelectionShader, "worldViewProjection");
        mColorScaleLoc = GLES20.glGetUniformLocation(mSelectionShader, "colorScale");

        // Shader with lights
        Log.i("Body", "Loading shader");
        mShaderWithLights = Programs.loadProgram(VERTEX_SHADER_LIGHT, FRAGMENT_SHADER_LIGHT);
        GLES20.glBindAttribLocation(mShaderWithLights, 0, "position");
        GLES20.glBindAttribLocation(mShaderWithLights, 1, "normal");
        GLES20.glBindAttribLocation(mShaderWithLights, 2, "texCoord");
        GLES20.glLinkProgram(mShaderWithLights);
        mWorldViewProjectionLoc =
                GLES20.glGetUniformLocation(mShaderWithLights, "worldViewProjection");
        mWorldViewLoc = GLES20.glGetUniformLocation(mShaderWithLights, "worldView");
        mOpacityLoc = GLES20.glGetUniformLocation(mShaderWithLights, "opacity");
        mTextureSamplerLoc = GLES20.glGetUniformLocation(mShaderWithLights, "textureSampler");
        GLES20.glReleaseShaderCompiler();

        // Kick of load.
        loadLayers(context, ui);
    }

    @Override
    public void opacitiesChanged() {
        float[] opacities = Layers.getOpacities();
        for (int i = 0; i < opacities.length; ++i) {
            mLayers[i].opacity.setFuture(opacities[i]);
        }
    }

    static final class Draw {
        String geometry;
        int offset, count;
    }

    static final class DrawGroup {
        public ShortBuffer vertexBufferData;
        public ShortBuffer indexBufferData;
        public ShortBuffer colorBufferData;
        public int numIndices;
        public int indexBuffer;
        public int vertexBuffer;
        public int colorBuffer;
        String texture;
        int diffuseTexture;

        // |loadedDiffuseTexture| is used for dynamically generated textures,
        // currently only one-color bitmaps. |loadedCompressedDiffuseTexture|
        // is used for "normal" textures.
        Bitmap loadedDiffuseTexture;
        public ETC1Texture loadedCompressedDiffuseTexture;

        public float[] diffuseColor;
        public ArrayList<Draw> draws;
    }

    private static final class Layer {
        public boolean isVisibleTarget;  // Used for picking in transparent layer.
        public int type;
        public Interpolant opacity;
        public DrawGroup[] drawGroups;
        public float renderOpacity;

        public Layer(int info) {
            this.type = info;
        }
    }

    @Override
    public void finishLayerLoad(LayersLoader.Results r, boolean isLoadDone) {
        Layer layer = mLayers[r.layerId];
        layer.drawGroups = r.groups;
        this.mSelectionColorMap = r.selectionColorMap;
        this.mMaxColorIndex = r.maxColorIndex;

        for (DrawGroup group : layer.drawGroups) {
            // TODO: maybe have a finer-grained callback so that not all layer textures
            // need to be in memory at once?
            if (group.loadedCompressedDiffuseTexture != null) {
              group.diffuseTexture = Textures.loadTexture(group.loadedCompressedDiffuseTexture);
              group.loadedCompressedDiffuseTexture = null;
            } else {
              group.diffuseTexture = Textures.loadTexture(group.loadedDiffuseTexture);
              group.loadedDiffuseTexture.recycle();
            }

            group.indexBuffer =
                    createShortBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, group.indexBufferData);
            group.indexBufferData = null;

            group.vertexBuffer = createShortBuffer(GLES20.GL_ARRAY_BUFFER, group.vertexBufferData);
            group.vertexBufferData = null;

            group.colorBuffer = createShortBuffer(GLES20.GL_ARRAY_BUFFER, group.colorBufferData);
            group.colorBufferData = null;
        }
    }
}




Java Source Code List

com.google.android.apps.body.Base.java
com.google.android.apps.body.BodyActivity.java
com.google.android.apps.body.BodyGLSurfaceView.java
com.google.android.apps.body.BodyJni.java
com.google.android.apps.body.BodyOpenGLRenderer.java
com.google.android.apps.body.BodySearchProvider.java
com.google.android.apps.body.BodyTosDialog.java
com.google.android.apps.body.FP.java
com.google.android.apps.body.Interpolant.java
com.google.android.apps.body.Label.java
com.google.android.apps.body.LayersLoader.java
com.google.android.apps.body.Layers.java
com.google.android.apps.body.Navigate.java
com.google.android.apps.body.QuickactionBackgroundDrawable.java
com.google.android.apps.body.QuickactionPopupWindow.java
com.google.android.apps.body.Render.java
com.google.android.apps.body.Select.java
com.google.android.apps.body.tdl.Programs.java
com.google.android.apps.body.tdl.TdlMath.java
com.google.android.apps.body.tdl.Textures.java