package min3d.core;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import min3d.Min3d;
import min3d.Shared;
import min3d.animation.AnimationObject3d;
import min3d.vos.FrustumManaged;
import min3d.vos.Light;
import min3d.vos.LightType;
import min3d.vos.RenderType;
import min3d.vos.TextureVo;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.util.Log;
public class Renderer implements GLSurfaceView.Renderer
{
public static final int NUM_GLLIGHTS = 8;
private GL10 _gl;
private Scene _scene;
private TextureManager _textureManager;
private float _surfaceAspectRatio;
private IntBuffer _scratchIntBuffer;
private FloatBuffer _scratchFloatBuffer;
private boolean _scratchB;
// stats-related
public static final int FRAMERATE_SAMPLEINTERVAL_MS = 1000;
private boolean _logFps = false;
private long _frameCount = 0;
private float _fps = 0;
private long _timeLastSample;
private ActivityManager _activityManager;
private ActivityManager.MemoryInfo _memoryInfo;
public Renderer(Scene $scene)
{
_scene = $scene;
_scratchIntBuffer = IntBuffer.allocate(4);
_scratchFloatBuffer = FloatBuffer.allocate(4);
_textureManager = new TextureManager();
Shared.textureManager(_textureManager);
_activityManager = (ActivityManager) Shared.context().getSystemService( Context.ACTIVITY_SERVICE );
_memoryInfo = new ActivityManager.MemoryInfo();
}
public void onSurfaceCreated(GL10 $gl, EGLConfig eglConfig)
{
Log.i(Min3d.TAG, "Renderer.onSurfaceCreated()");
RenderCaps.setRenderCaps($gl);
setGl($gl);
reset();
_scene.init();
}
public void onSurfaceChanged(GL10 gl, int w, int h)
{
Log.i(Min3d.TAG, "Renderer.onSurfaceChanged()");
setGl(_gl);
_surfaceAspectRatio = (float)w / (float)h;
_gl.glViewport(0, 0, w, h);
_gl.glMatrixMode(GL10.GL_PROJECTION);
_gl.glLoadIdentity();
updateViewFrustrum();
}
public void onDrawFrame(GL10 gl)
{
// Update 'model'
_scene.update();
// Update 'view'
drawSetup();
drawScene();
if (_logFps) doFps();
}
//
/**
* Accessor to the GL object, in case anything outside this class wants to do
* bad things with it :)
*/
public GL10 gl()
{
return _gl;
}
/**
* Returns last sampled framerate (logFps must be set to true)
*/
public float fps()
{
return _fps;
}
/**
* Return available system memory in bytes
*/
public long availMem()
{
_activityManager.getMemoryInfo(_memoryInfo);
return _memoryInfo.availMem;
}
protected void drawSetup()
{
// View frustrum
if (_scene.camera().frustum.isDirty()) {
updateViewFrustrum();
}
// Camera
_gl.glMatrixMode(GL10.GL_MODELVIEW);
_gl.glLoadIdentity();
GLU.gluLookAt(_gl,
_scene.camera().position.x,_scene.camera().position.y,_scene.camera().position.z,
_scene.camera().target.x,_scene.camera().target.y,_scene.camera().target.z,
_scene.camera().upAxis.x,_scene.camera().upAxis.y,_scene.camera().upAxis.z);
// Background color
if (_scene.backgroundColor().isDirty())
{
if (_scene.backgroundTransparent() == true)
_gl.glClearColor(0,0,0,0);
else
_gl.glClearColor(
(float)_scene.backgroundColor().r() / 255f,
(float)_scene.backgroundColor().g() / 255f,
(float)_scene.backgroundColor().b() / 255f,
(float)_scene.backgroundColor().a() / 255f);
_scene.backgroundColor().clearDirtyFlag();
}
_gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
drawSetupLights();
// Always on:
_gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
}
protected void drawSetupLights()
{
// GL_LIGHTS enabled/disabled based on enabledDirty list
for (int glIndex = 0; glIndex < NUM_GLLIGHTS; glIndex++)
{
if (_scene.lights().glIndexEnabledDirty()[glIndex] == true)
{
if (_scene.lights().glIndexEnabled()[glIndex] == true)
{
_gl.glEnable(GL10.GL_LIGHT0 + glIndex);
// make light's properties dirty to force update
_scene.lights().getLightByGlIndex(glIndex).setAllDirty();
}
else
{
_gl.glDisable(GL10.GL_LIGHT0 + glIndex);
}
_scene.lights().glIndexEnabledDirty()[glIndex] = false; // clear dirtyflag
}
}
// Lights' properties
Light[] lights = _scene.lights().toArray();
for (int i = 0; i < lights.length; i++)
{
Light light = lights[i];
if (light.isDirty()) // .. something has changed
{
// Check all of Light's properties for dirty
int glLightId = GL10.GL_LIGHT0 + _scene.lights().getGlIndexByLight(light);
if (light.position.isDirty())
{
light.commitPositionAndTypeBuffer();
_gl.glLightfv(glLightId, GL10.GL_POSITION, light.positionAndTypeBuffer());
light.position.clearDirtyFlag();
}
if (light.ambient.isDirty())
{
light.ambient.commitToFloatBuffer();
_gl.glLightfv(glLightId, GL10.GL_AMBIENT, light.ambient.floatBuffer());
light.ambient.clearDirtyFlag();
}
if (light.diffuse.isDirty())
{
light.diffuse.commitToFloatBuffer();
_gl.glLightfv(glLightId, GL10.GL_DIFFUSE, light.diffuse.floatBuffer());
light.diffuse.clearDirtyFlag();
}
if (light.specular.isDirty())
{
light.specular.commitToFloatBuffer();
_gl.glLightfv(glLightId, GL10.GL_SPECULAR, light.specular.floatBuffer());
light.specular.clearDirtyFlag();
}
if (light.emissive.isDirty())
{
light.emissive.commitToFloatBuffer();
_gl.glLightfv(glLightId, GL10.GL_EMISSION, light.emissive.floatBuffer());
light.emissive.clearDirtyFlag();
}
if(light.type() == LightType.DIRECTIONAL && light.direction.isDirty())
{
light.direction.commitToFloatBuffer();
_gl.glLightfv(glLightId, GL10.GL_SPOT_DIRECTION, light.direction.floatBuffer());
_gl.glLightf(glLightId, GL10.GL_SPOT_CUTOFF, 45f);
light.direction.clearDirtyFlag();
}
if (light.isVisibleBm().isDirty())
{
if (light.isVisible()) {
_gl.glEnable(glLightId);
} else {
_gl.glDisable(glLightId);
}
light.isVisibleBm().clearDirtyFlag();
}
if (light.attenuation().isDirty())
{
_gl.glLightf(glLightId, GL10.GL_CONSTANT_ATTENUATION, light.attenuation().getX());
_gl.glLightf(glLightId, GL10.GL_LINEAR_ATTENUATION, light.attenuation().getY());
_gl.glLightf(glLightId, GL10.GL_QUADRATIC_ATTENUATION, light.attenuation().getZ());
}
light.clearDirtyFlag();
}
}
}
protected void drawScene()
{
if(_scene.fogEnabled() == true) {
_gl.glFogf(GL10.GL_FOG_MODE, _scene.fogType().glValue());
_gl.glFogf(GL10.GL_FOG_START, _scene.fogNear());
_gl.glFogf(GL10.GL_FOG_END, _scene.fogFar());
_gl.glFogfv(GL10.GL_FOG_COLOR, _scene.fogColor().toFloatBuffer() );
_gl.glEnable(GL10.GL_FOG);
} else {
_gl.glDisable(GL10.GL_FOG);
}
for (int i = 0; i < _scene.children().size(); i++)
{
Object3d o = _scene.children().get(i);
if(o.animationEnabled())
{
((AnimationObject3d)o).update();
}
drawObject(o);
}
}
//boolean customResult = o.customRenderer(_gl);
//if (customResult) return;
protected void drawObject(Object3d $o)
{
if ($o.isVisible() == false) return;
// Various per-object settings:
// Normals
if ($o.hasNormals() && $o.normalsEnabled()) {
$o.vertices().normals().buffer().position(0);
_gl.glNormalPointer(GL10.GL_FLOAT, 0, $o.vertices().normals().buffer());
_gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
}
else {
_gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
}
// Is lighting enabled for object...
/*
// *** this version not working properly on emulator - why not? ***
_scratchIntBuffer.position(0);
_gl.glGetIntegerv(GL10.GL_LIGHTING, _scratchIntBuffer);
if (useLighting != _scratchIntBuffer.get(0))
{
if (useLighting == 1) {
_gl.glEnable(GL10.GL_LIGHTING);
} else {
_gl.glDisable(GL10.GL_LIGHTING);
}
}
*/
boolean useLighting = (_scene.lightingEnabled() && $o.hasNormals() && $o.normalsEnabled() && $o.lightingEnabled());
if (useLighting) {
_gl.glEnable(GL10.GL_LIGHTING);
} else {
_gl.glDisable(GL10.GL_LIGHTING);
}
// Shademodel
_gl.glGetIntegerv(GL11.GL_SHADE_MODEL, _scratchIntBuffer);
if ($o.shadeModel().glConstant() != _scratchIntBuffer.get(0)) {
_gl.glShadeModel($o.shadeModel().glConstant());
}
// Colors: either per-vertex, or per-object
if ($o.hasVertexColors() && $o.vertexColorsEnabled()) {
$o.vertices().colors().buffer().position(0);
_gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, $o.vertices().colors().buffer());
_gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
}
else {
_gl.glColor4f(
(float)$o.defaultColor().r / 255f,
(float)$o.defaultColor().g / 255f,
(float)$o.defaultColor().b / 255f,
(float)$o.defaultColor().a / 255f
);
_gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
// Colormaterial
_gl.glGetIntegerv(GL10.GL_COLOR_MATERIAL, _scratchIntBuffer);
_scratchB = (_scratchIntBuffer.get(0) != 0);
if ($o.colorMaterialEnabled() != _scratchB) {
if ($o.colorMaterialEnabled())
_gl.glEnable(GL10.GL_COLOR_MATERIAL);
else
_gl.glDisable(GL10.GL_COLOR_MATERIAL);
}
// Point size
if ($o.renderType() == RenderType.POINTS)
{
if ($o.pointSmoothing())
_gl.glEnable(GL10.GL_POINT_SMOOTH);
else
_gl.glDisable(GL10.GL_POINT_SMOOTH);
_gl.glPointSize($o.pointSize());
}
// Line properties
if ($o.renderType() == RenderType.LINES || $o.renderType() == RenderType.LINE_STRIP || $o.renderType() == RenderType.LINE_LOOP)
{
if ( $o.lineSmoothing() == true) {
_gl.glEnable(GL10.GL_LINE_SMOOTH);
}
else {
_gl.glDisable(GL10.GL_LINE_SMOOTH);
}
_gl.glLineWidth($o.lineWidth());
}
// Backface culling
if ($o.doubleSidedEnabled()) {
_gl.glDisable(GL10.GL_CULL_FACE);
}
else {
_gl.glEnable(GL10.GL_CULL_FACE);
}
drawObject_textures($o);
// Matrix operations in modelview
_gl.glPushMatrix();
// from NyARToolkit
_gl.glLoadMatrixf($o.matrix(), 0);
_gl.glTranslatef($o.position().x, $o.position().y, $o.position().z);
_gl.glRotatef($o.rotation().x, 1,0,0);
_gl.glRotatef($o.rotation().y, 0,1,0);
_gl.glRotatef($o.rotation().z, 0,0,1);
_gl.glScalef($o.scale().x, $o.scale().y, $o.scale().z);
// Draw
$o.vertices().points().buffer().position(0);
_gl.glVertexPointer(3, GL10.GL_FLOAT, 0, $o.vertices().points().buffer());
if (! $o.ignoreFaces())
{
int pos, len;
if (! $o.faces().renderSubsetEnabled()) {
pos = 0;
len = $o.faces().size();
}
else {
pos = $o.faces().renderSubsetStartIndex() * FacesBufferedList.PROPERTIES_PER_ELEMENT;
len = $o.faces().renderSubsetLength();
}
$o.faces().buffer().position(pos);
_gl.glDrawElements(
$o.renderType().glValue(),
len * FacesBufferedList.PROPERTIES_PER_ELEMENT,
GL10.GL_UNSIGNED_SHORT,
$o.faces().buffer());
}
else
{
_gl.glDrawArrays($o.renderType().glValue(), 0, $o.vertices().size());
}
//
// Recurse on children
//
if ($o instanceof Object3dContainer)
{
Object3dContainer container = (Object3dContainer)$o;
for (int i = 0; i < container.children().size(); i++)
{
Object3d o = container.children().get(i);
drawObject(o);
}
}
// Restore matrix
_gl.glPopMatrix();
}
private void drawObject_textures(Object3d $o)
{
// iterate thru object's textures
for (int i = 0; i < RenderCaps.maxTextureUnits(); i++)
{
_gl.glActiveTexture(GL10.GL_TEXTURE0 + i);
_gl.glClientActiveTexture(GL10.GL_TEXTURE0 + i);
if ($o.hasUvs() && $o.texturesEnabled())
{
$o.vertices().uvs().buffer().position(0);
_gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, $o.vertices().uvs().buffer());
TextureVo textureVo = ((i < $o.textures().size())) ? textureVo = $o.textures().get(i) : null;
if (textureVo != null)
{
// activate texture
int glId = _textureManager.getGlTextureId(textureVo.textureId);
_gl.glBindTexture(GL10.GL_TEXTURE_2D, glId);
_gl.glEnable(GL10.GL_TEXTURE_2D);
_gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
int minFilterType = _textureManager.hasMipMap(textureVo.textureId) ? GL10.GL_LINEAR_MIPMAP_NEAREST : GL10.GL_NEAREST;
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilterType);
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // (OpenGL default)
// do texture environment settings
for (int j = 0; j < textureVo.textureEnvs.size(); j++)
{
_gl.glTexEnvx(GL10.GL_TEXTURE_ENV, textureVo.textureEnvs.get(j).pname, textureVo.textureEnvs.get(j).param);
}
// texture wrapping settings
_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, (textureVo.repeatU ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE));
_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, (textureVo.repeatV ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE));
// texture offset, if any
if (textureVo.offsetU != 0 || textureVo.offsetV != 0)
{
_gl.glMatrixMode(GL10.GL_TEXTURE);
_gl.glLoadIdentity();
_gl.glTranslatef(textureVo.offsetU, textureVo.offsetV, 0);
_gl.glMatrixMode(GL10.GL_MODELVIEW); // .. restore matrixmode
}
}
else
{
_gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
_gl.glDisable(GL10.GL_TEXTURE_2D);
_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
}
else
{
_gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
_gl.glDisable(GL10.GL_TEXTURE_2D);
_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
}
}
/**
* Used by TextureManager
*/
int uploadTextureAndReturnId(Bitmap $bitmap, boolean $generateMipMap) /*package-private*/
{
int glTextureId;
int[] a = new int[1];
_gl.glGenTextures(1, a, 0); // create a 'texture name' and put it in array element 0
glTextureId = a[0];
_gl.glBindTexture(GL10.GL_TEXTURE_2D, glTextureId);
if($generateMipMap && _gl instanceof GL11) {
_gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
} else {
_gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_FALSE);
}
// 'upload' to gpu
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, $bitmap, 0);
return glTextureId;
}
/**
* Used by TextureManager
*/
void deleteTexture(int $glTextureId) /*package-private*/
{
int[] a = new int[1];
a[0] = $glTextureId;
_gl.glDeleteTextures(1, a, 0);
}
protected void updateViewFrustrum()
{
FrustumManaged vf = _scene.camera().frustum;
float n = vf.shortSideLength() / 2f;
float lt, rt, btm, top;
lt = vf.horizontalCenter() - n*_surfaceAspectRatio;
rt = vf.horizontalCenter() + n*_surfaceAspectRatio;
btm = vf.verticalCenter() - n*1;
top = vf.verticalCenter() + n*1;
if (_surfaceAspectRatio > 1) {
lt *= 1f/_surfaceAspectRatio;
rt *= 1f/_surfaceAspectRatio;
btm *= 1f/_surfaceAspectRatio;
top *= 1f/_surfaceAspectRatio;
}
_gl.glMatrixMode(GL10.GL_PROJECTION);
_gl.glLoadIdentity();
_gl.glFrustumf(lt,rt, btm,top, vf.zNear(), vf.zFar());
vf.clearDirtyFlag();
}
/**
* If true, framerate and memory is periodically calculated and Log'ed,
* and gettable thru fps()
*/
public void logFps(boolean $b)
{
_logFps = $b;
if (_logFps) { // init
_timeLastSample = System.currentTimeMillis();
_frameCount = 0;
}
}
private void setGl(GL10 $gl)
{
_gl = $gl;
}
private void doFps()
{
_frameCount++;
long now = System.currentTimeMillis();
long delta = now - _timeLastSample;
if (delta >= FRAMERATE_SAMPLEINTERVAL_MS)
{
_fps = _frameCount / (delta/1000f);
_activityManager.getMemoryInfo(_memoryInfo);
Log.v(Min3d.TAG, "FPS: " + Math.round(_fps) + ", availMem: " + Math.round(_memoryInfo.availMem/1048576) + "MB");
_timeLastSample = now;
_frameCount = 0;
}
}
private void reset()
{
// Reset TextureManager
Shared.textureManager().reset();
// Do OpenGL settings which we are using as defaults, or which we will not be changing on-draw
// Explicit depth settings
_gl.glEnable(GL10.GL_DEPTH_TEST);
_gl.glClearDepthf(1.0f);
_gl.glDepthFunc(GL10.GL_LESS);
_gl.glDepthRangef(0,1f);
_gl.glDepthMask(true);
// Alpha enabled
_gl.glEnable(GL10.GL_BLEND);
_gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
// "Transparency is best implemented using glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
// with primitives sorted from farthest to nearest."
// Texture
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); // (OpenGL default is GL_NEAREST_MIPMAP)
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // (is OpenGL default)
// CCW frontfaces only, by default
_gl.glFrontFace(GL10.GL_CCW);
_gl.glCullFace(GL10.GL_BACK);
_gl.glEnable(GL10.GL_CULL_FACE);
// Disable lights by default
for (int i = GL10.GL_LIGHT0; i < GL10.GL_LIGHT0 + NUM_GLLIGHTS; i++) {
_gl.glDisable(i);
}
//
// Scene object init only happens here, when we get GL for the first time
//
}
// for NyARToolkit I/F
private static final int MARKER_MAX = 8;
private int found_markers;
private int [] ar_code_index = new int[MARKER_MAX];
private float [][] resultf = new float[MARKER_MAX][16];
private float [] cameraRHf = new float[16];
public void objectPointChanged(int found_markers, int [] ar_code_index, float[][] resultf,
float[] cameraRHf) {
synchronized (this) {
this.found_markers = found_markers;
for (int i = 0; i < MARKER_MAX; i++) {
this.ar_code_index[i] = ar_code_index[i];
System.arraycopy(resultf[i], 0, this.resultf[i], 0, 16);
}
System.arraycopy(cameraRHf, 0, this.cameraRHf, 0, 16);
}
objectClear();
for (int i = 0; i < MARKER_MAX; i++) {
if (ar_code_index[i] > (_scene.children().size() - 1))
continue;
Log.i("min3D Render","Draw Model number:" + ar_code_index[i]);
Object3d o = _scene.children().get(ar_code_index[i]);
if (!o.isVisible()) {
if (o.animationEnabled())
((AnimationObject3d)o).play();
o.isVisible(true);
o.matrix(resultf[i]);
}
}
}
public void objectClear() {
for (int i = 0; i < _scene.children().size(); i++)
{
Object3d o = _scene.children().get(i);
if (o.animationEnabled())
((AnimationObject3d)o).pause();
o.isVisible(false);
}
}
}
//Color4 c = new Color4(100,100,0, 255);
//_gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_EMISSION, c.toFloatBuffer());
///Color4 c = new Color4(0,128,0, 255);
///_gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, c.toFloatBuffer());
//Color4 c = new Color4(128,128,128,128);
//_gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, c.toFloatBuffer());