François Gautrais 9 năm trước cách đây
mục cha
commit
b569888192
100 tập tin đã thay đổi với 9410 bổ sung0 xóa
  1. 1 0
      app/.gitignore
  2. 27 0
      app/build.gradle
  3. 17 0
      app/proguard-rules.pro
  4. 40 0
      app/src/main/AndroidManifest.xml
  5. 90 0
      app/src/main/java/app/brest/app/brest/ui/CameraPreview.java
  6. 306 0
      app/src/main/java/app/brest/testmin3d/ARActivity.java
  7. 52 0
      app/src/main/java/app/brest/testmin3d/SplashActivity.java
  8. 163 0
      app/src/main/java/app/brest/testmin3d/ViewerActivity.java
  9. 44 0
      app/src/main/java/app/brest/utils/JSONLoader.java
  10. 86 0
      app/src/main/java/app/brest/utils/ResourceManager.java
  11. 178 0
      app/src/main/java/app/brest/utils/SensorsManager.java
  12. 80 0
      app/src/main/java/app/brest/utils/SlideBuffer.java
  13. 139 0
      app/src/main/java/app/brest/utils/app/brest/game/Area.java
  14. 294 0
      app/src/main/java/app/brest/utils/app/brest/game/Game.java
  15. 106 0
      app/src/main/java/app/brest/utils/app/brest/game/Place.java
  16. 71 0
      app/src/main/java/app/brest/utils/app/brest/game/Player.java
  17. 131 0
      app/src/main/java/app/brest/utils/app/brest/game/Resource.java
  18. 85 0
      app/src/main/java/app/brest/utils/geometry/GPSPoint.java
  19. 98 0
      app/src/main/java/app/brest/utils/geometry/Point.java
  20. 120 0
      app/src/main/java/app/brest/utils/geometry/Shape.java
  21. 17 0
      app/src/main/java/min3d/Min3d.java
  22. 46 0
      app/src/main/java/min3d/Shared.java
  23. 84 0
      app/src/main/java/min3d/Utils.java
  24. 187 0
      app/src/main/java/min3d/animation/AnimationObject3d.java
  25. 103 0
      app/src/main/java/min3d/animation/KeyFrame.java
  26. 160 0
      app/src/main/java/min3d/core/Color4BufferList.java
  27. 190 0
      app/src/main/java/min3d/core/FacesBufferedList.java
  28. 149 0
      app/src/main/java/min3d/core/ManagedLightList.java
  29. 157 0
      app/src/main/java/min3d/core/Number3dBufferList.java
  30. 491 0
      app/src/main/java/min3d/core/Object3d.java
  31. 137 0
      app/src/main/java/min3d/core/Object3dContainer.java
  32. 153 0
      app/src/main/java/min3d/core/RenderCaps.java
  33. 682 0
      app/src/main/java/min3d/core/Renderer.java
  34. 204 0
      app/src/main/java/min3d/core/RendererActivity.java
  35. 297 0
      app/src/main/java/min3d/core/Scene.java
  36. 155 0
      app/src/main/java/min3d/core/TextureList.java
  37. 165 0
      app/src/main/java/min3d/core/TextureManager.java
  38. 137 0
      app/src/main/java/min3d/core/UvBufferList.java
  39. 183 0
      app/src/main/java/min3d/core/Vertices.java
  40. 8 0
      app/src/main/java/min3d/interfaces/IDirtyManaged.java
  41. 6 0
      app/src/main/java/min3d/interfaces/IDirtyParent.java
  42. 20 0
      app/src/main/java/min3d/interfaces/IObject3dContainer.java
  43. 39 0
      app/src/main/java/min3d/interfaces/ISceneController.java
  44. 114 0
      app/src/main/java/min3d/objectPrimitives/Box.java
  45. 142 0
      app/src/main/java/min3d/objectPrimitives/HollowCylinder.java
  46. 57 0
      app/src/main/java/min3d/objectPrimitives/Rectangle.java
  47. 106 0
      app/src/main/java/min3d/objectPrimitives/SkyBox.java
  48. 127 0
      app/src/main/java/min3d/objectPrimitives/Sphere.java
  49. 115 0
      app/src/main/java/min3d/objectPrimitives/Torus.java
  50. 398 0
      app/src/main/java/min3d/parser/AParser.java
  51. 27 0
      app/src/main/java/min3d/parser/IParser.java
  52. 157 0
      app/src/main/java/min3d/parser/LittleEndianDataInputStream.java
  53. 239 0
      app/src/main/java/min3d/parser/MD2Parser.java
  54. 230 0
      app/src/main/java/min3d/parser/Max3DSParser.java
  55. 265 0
      app/src/main/java/min3d/parser/ObjParser.java
  56. 165 0
      app/src/main/java/min3d/parser/ParseObjectData.java
  57. 12 0
      app/src/main/java/min3d/parser/ParseObjectFace.java
  58. 40 0
      app/src/main/java/min3d/parser/Parser.java
  59. 32 0
      app/src/main/java/min3d/vos/AbstractDirtyManaged.java
  60. 24 0
      app/src/main/java/min3d/vos/BooleanManaged.java
  61. 19 0
      app/src/main/java/min3d/vos/CameraVo.java
  62. 100 0
      app/src/main/java/min3d/vos/Color4.java
  63. 202 0
      app/src/main/java/min3d/vos/Color4Managed.java
  64. 29 0
      app/src/main/java/min3d/vos/Face.java
  65. 24 0
      app/src/main/java/min3d/vos/FloatManaged.java
  66. 21 0
      app/src/main/java/min3d/vos/FogType.java
  67. 99 0
      app/src/main/java/min3d/vos/FrustumManaged.java
  68. 228 0
      app/src/main/java/min3d/vos/Light.java
  69. 20 0
      app/src/main/java/min3d/vos/LightType.java
  70. 165 0
      app/src/main/java/min3d/vos/Number3d.java
  71. 139 0
      app/src/main/java/min3d/vos/Number3dManaged.java
  72. 26 0
      app/src/main/java/min3d/vos/RenderType.java
  73. 23 0
      app/src/main/java/min3d/vos/ShadeModel.java
  74. 25 0
      app/src/main/java/min3d/vos/ShadeModelManaged.java
  75. 34 0
      app/src/main/java/min3d/vos/TexEnvxVo.java
  76. 61 0
      app/src/main/java/min3d/vos/TextureVo.java
  77. 28 0
      app/src/main/java/min3d/vos/Uv.java
  78. 19 0
      app/src/main/java/min3d/vos/Vertex3d.java
  79. BIN
      app/src/main/res/drawable/barong.jpg
  80. BIN
      app/src/main/res/drawable/camaro.jpg
  81. BIN
      app/src/main/res/drawable/ceiling.jpg
  82. BIN
      app/src/main/res/drawable/checkerboard.png
  83. BIN
      app/src/main/res/drawable/clouds_alpha2b.png
  84. BIN
      app/src/main/res/drawable/deadmickey.jpg
  85. BIN
      app/src/main/res/drawable/earth.jpg
  86. BIN
      app/src/main/res/drawable/face_eyel_hi.jpg
  87. BIN
      app/src/main/res/drawable/face_eyer_hi.jpg
  88. BIN
      app/src/main/res/drawable/face_skin_hi.jpg
  89. BIN
      app/src/main/res/drawable/face_sock.jpg
  90. BIN
      app/src/main/res/drawable/floor.jpg
  91. BIN
      app/src/main/res/drawable/icon.png
  92. BIN
      app/src/main/res/drawable/jupiter.jpg
  93. BIN
      app/src/main/res/drawable/maqjpg.jpg
  94. BIN
      app/src/main/res/drawable/monster.jpg
  95. BIN
      app/src/main/res/drawable/moon.jpg
  96. BIN
      app/src/main/res/drawable/ogrobase.jpg
  97. BIN
      app/src/main/res/drawable/revenant.jpg
  98. BIN
      app/src/main/res/drawable/stonetexture.jpg
  99. BIN
      app/src/main/res/drawable/uglysquares.png
  100. BIN
      app/src/main/res/drawable/white_with_alpha_hole.png

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 27 - 0
app/build.gradle

@@ -0,0 +1,27 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 24
+    buildToolsVersion "24.0.1"
+
+    defaultConfig {
+        applicationId "app.brest.testmin3d"
+        minSdkVersion 14
+        targetSdkVersion 24
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    testCompile 'junit:junit:4.12'
+    compile 'com.android.support:appcompat-v7:24.1.1'
+    compile 'com.android.support:support-v4:24.1.1'
+}

+ 17 - 0
app/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /opt/android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 40 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="app.brest.testmin3d">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera2.full" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name=".ARActivity"></activity>
+        <activity
+            android:name=".SplashActivity"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".ViewerActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:label="@string/title_activity_viewer"
+            android:theme="@style/FullscreenTheme"></activity>
+    </application>
+
+</manifest>

+ 90 - 0
app/src/main/java/app/brest/app/brest/ui/CameraPreview.java

@@ -0,0 +1,90 @@
+package app.brest.app.brest.ui;
+
+/**
+ * Created by ptitcois on 17/08/16.
+ */
+
+import java.io.IOException;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import android.hardware.Camera;
+import android.content.Context;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import java.io.IOException;
+
+public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
+
+    private Camera mCamera;
+    private SurfaceHolder mHolder;
+
+    public CameraPreview(Context context, Camera camera) {
+        super(context);
+        this.mCamera = camera;
+        this.mHolder = getHolder();
+        this.mHolder.addCallback(this);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        try {
+            this.mCamera.setPreviewDisplay(holder);
+            this.mCamera.startPreview();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+
+
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        this.getHolder().removeCallback(this);
+        this.mCamera.stopPreview();
+        this.mCamera.release();
+    }
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
+    {
+        if (mHolder.getSurface() == null) {
+            // preview surface does not exist
+            return;
+        }
+
+        // stop preview before making changes
+        try {
+            mCamera.stopPreview();
+        } catch (Exception e) {
+            // ignore: tried to stop a non-existent preview
+        }
+
+        // make any resize, rotate or reformatting changes here
+        if (this.getResources().getConfiguration().orientation != getResources().getConfiguration().ORIENTATION_LANDSCAPE) {
+
+            mCamera.setDisplayOrientation(90);
+
+        } else {
+
+            mCamera.setDisplayOrientation(0);
+
+        }
+        // start preview with new settings
+        try {
+            mCamera.setPreviewDisplay(mHolder);
+            mCamera.startPreview();
+
+        } catch (Exception e) {
+            Log.d("TAG", "Error starting camera preview: " + e.getMessage());
+        }
+
+    }
+}

+ 306 - 0
app/src/main/java/app/brest/testmin3d/ARActivity.java

@@ -0,0 +1,306 @@
+package app.brest.testmin3d;
+import android.app.Activity;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import app.brest.app.brest.ui.CameraPreview;
+import app.brest.utils.JSONLoader;
+import app.brest.utils.ResourceManager;
+import app.brest.utils.SensorsManager;
+import app.brest.utils.app.brest.game.Game;
+import app.brest.utils.app.brest.game.Resource;
+import min3d.core.Object3d;
+import min3d.core.Object3dContainer;
+import min3d.core.RendererActivity;
+import min3d.parser.IParser;
+import min3d.parser.Parser;
+import min3d.vos.Light;
+import min3d.vos.Number3d;
+
+
+public class ARActivity extends RendererActivity
+{
+
+    private CameraPreview mImageSurfaceView;
+    private Camera camera;
+    private FrameLayout cameraPreviewLayout;
+
+    private Object3dContainer faceObject3D;
+    private Button mOkButton;
+    private TextView mAngle;
+    private TextView mGps;
+    private final Object mLock = new Object();
+    protected boolean isPausing=false;
+
+    private Game mGame;
+
+
+    public void init()
+    {
+        setContentView(R.layout.activity_main);
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+
+        cameraPreviewLayout = (FrameLayout)findViewById(R.id.camera_preview);
+
+        camera = checkDeviceCamera();
+        mImageSurfaceView = new CameraPreview(ARActivity.this, camera);
+
+        mAngle = (TextView) findViewById(R.id.angle);
+        mGps = (TextView) findViewById(R.id.gps);
+
+
+        mOkButton = (Button) findViewById(R.id.button_catch);
+        mOkButton.setVisibility(View.INVISIBLE);
+
+    }
+
+    private Camera checkDeviceCamera() {
+        Camera mCamera = null;
+        try {
+            mCamera = Camera.open();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mCamera;
+    }
+
+    protected void onCreateSetContentView()
+    {
+        init();
+        cameraPreviewLayout.addView(mImageSurfaceView,0);
+        cameraPreviewLayout.addView(_glSurfaceView,1);
+
+    }
+
+    @Override
+    protected void glSurfaceViewConfig()
+    {
+        // !important
+        _glSurfaceView.setEGLConfigChooser(8,8,8,8, 16, 0);
+        _glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+    }
+
+    @Override
+    public void initScene()
+    {
+        // !important
+        scene.backgroundColor().setAll(0x00000000);
+        scene.lights().add(new Light());
+        scene.lights().add(new Light());
+
+        Light myLight = new Light();
+        myLight.position.setZ(150);
+        scene.lights().add(myLight);
+
+
+        //IParser myParser = Parser.createParser(Parser.Type.OBJ, getResources(), "app.brest.testmin3d:raw/face_obj", true);
+        //myParser.parse();
+
+
+
+    }
+
+    protected Number3d pos(float deltaAngle, int _radius)
+    {
+        float radius = _radius;
+        return new Number3d((float)(radius*Math.sin(deltaAngle)),
+                            0,
+                            (float)(15+radius*Math.cos(deltaAngle))
+                        );
+    }
+
+    protected boolean updateSceneResource()
+    {
+
+        ArrayList<Resource> res = mGame.getResourcesNextToPlayer();
+        final TextView tv = (TextView) findViewById(R.id.text_detected);
+        final TextView tv2 = (TextView)findViewById(R.id.text_place);
+        String toDisplay ="Detetected \n";
+       // String dete = "Detected:\n";
+        scene.clear();
+        for (int i = 0; i < res.size(); i++) {
+            Resource rr = res.get(i);
+            toDisplay+="\t"+rr.getName()+" \n";
+            //dete += "\t" + rr.getName() + "\n";
+            Object3d oo = rr.get3DModel(this);
+            scene.addChild(oo);
+        }
+        final String td = toDisplay;
+        mAngle.getHandler().post(new Runnable() {
+            public void run() {
+                tv2.setText(td);
+            }
+        });
+
+        final String dete2 = mGame.getResourcesNextToPlayer2();
+        tv.getHandler().post(new Runnable() {
+            public void run() {
+                tv.setText(dete2);
+            }
+        });
+
+
+        return scene.numChildren()>0;
+    }
+     @Override
+    protected void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        Game g = Game.load(this);
+
+        if(getIntent().hasExtra("game_name"))
+        {
+            String n = getIntent().getStringExtra("game_name");
+            mGame = new Game(n, this);
+        }else if (g!= null) {
+            mGame = g;
+            mGame.newSensorManager(this);
+        }else
+        {
+            throw new Error("Le jeu n'est pas sauvegardé");
+        }
+
+    }
+
+
+
+    @Override
+    /**
+     * Verifie si on doit afficher des resources et gere le bouton de capture
+     */
+    public  void updateScene()
+    {
+        boolean isResource = false;
+        synchronized (mLock) {
+            if (!isPausing) {
+                boolean b = mGame.precache3DResource(this);
+                isResource = updateSceneResource();
+                if(b)
+                {
+                    final Activity _this = this;
+                    mAngle.getHandler().post(new Runnable() {
+                        public void run() {
+                            Toast.makeText(_this,"Localisé dans la zone", Toast.LENGTH_LONG);
+                        }
+                    });
+
+                }
+            }else
+            {
+                scene.clear();
+                mGame.removeCached3DModels();
+            }
+        }
+
+        if(isResource) {
+            mOkButton.getHandler().post(new Runnable() {
+                public void run() {
+                    mOkButton.setVisibility(View.VISIBLE);
+                }
+            });
+        }else
+        {
+            mOkButton.getHandler().post(new Runnable() {
+                public void run() {
+                    mOkButton.setVisibility(View.INVISIBLE);
+                }
+            });
+
+        }
+        mAngle.getHandler().post(new Runnable() {
+            public void run() {
+                mAngle.setText("Angle X: "+mGame.getPlayer().getOrientation()+"°\n Angle Y: "+
+                                mGame.getPlayer().getOrientationY()
+                                +"\n Angle Z: "+mGame.getPlayer().getOrientationZ()+"°");
+            }
+        });
+
+    }
+
+
+    @Override
+    protected  void onResume() {
+        super.onResume();
+        mGame.onResume(this);
+        scene.clear();
+
+        synchronized (mLock)
+        {
+            isPausing=false;
+        }
+    }
+    @Override
+    protected  void onPause()
+    {
+        synchronized (mLock)
+        {
+            isPausing=true;
+        }
+        super.onPause();
+        mGame.onPause(this);
+        scene.clear();
+
+    }
+
+    public void setGpsText(final double lat, final double lon)
+    {
+        mGps.getHandler().post(new Runnable() {
+
+            public void run() {
+                mGps.setText("Latitude: "+lat+"°\n Longitude: "+lon+"\n");
+            }
+        });
+    }
+
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        // Save the user's current game state
+        savedInstanceState.putSerializable("game", mGame);
+
+        // Always call the superclass so it can save the view hierarchy state
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    public void onClickPickUp(View v)
+    {
+        boolean stageFinished = mGame.pickResource();
+        boolean finished = false;
+
+        //TDOD: Nouvelle activité pour afficher la ressource
+
+        if(stageFinished)
+        {
+           finished = mGame.nextStage();
+        }
+
+        if(finished)
+        {
+
+        }
+
+    }
+
+
+    public void logCuurentAreas()
+    {
+
+    }
+
+}

+ 52 - 0
app/src/main/java/app/brest/testmin3d/SplashActivity.java

@@ -0,0 +1,52 @@
+package app.brest.testmin3d;
+
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import app.brest.utils.app.brest.game.Game;
+import app.brest.utils.geometry.GPSPoint;
+
+public class SplashActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_splash);
+        checkSaved();
+        GPSPoint a = new GPSPoint(48.3845125, -4.493831);
+        GPSPoint b = new GPSPoint(48.3840495, -4.5037464);
+        Log.e("GPS", "OUT:"+a.getDistanceWith(b));
+                ;
+    }
+
+    public void onClickNew(View v)
+    {
+        Intent intent = new Intent(this, ARActivity.class);
+        intent.putExtra("game_name", "game_medium");
+        startActivity(intent);
+    }
+
+    public void onClickContinue(View v)
+    {
+        Intent intent = new Intent(this, ARActivity.class);
+        startActivity(intent);
+    }
+
+    protected void onResume()
+    {
+        super.onResume();
+        checkSaved();
+    }
+
+    protected void checkSaved()
+    {
+        View v = (View) findViewById(R.id.button_continue);
+        if(Game.isSaved(this))
+            v.setVisibility(View.VISIBLE);
+        else
+            v.setVisibility(View.INVISIBLE);
+    }
+}

+ 163 - 0
app/src/main/java/app/brest/testmin3d/ViewerActivity.java

@@ -0,0 +1,163 @@
+package app.brest.testmin3d;
+
+import android.annotation.SuppressLint;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * An example full-screen activity that shows and hides the system UI (i.e.
+ * status bar and navigation/system bar) with user interaction.
+ */
+public class ViewerActivity extends AppCompatActivity {
+    /**
+     * Whether or not the system UI should be auto-hidden after
+     * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
+     */
+    private static final boolean AUTO_HIDE = true;
+
+    /**
+     * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
+     * user interaction before hiding the system UI.
+     */
+    private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
+
+    /**
+     * Some older devices needs a small delay between UI widget updates
+     * and a change of the status and navigation bar.
+     */
+    private static final int UI_ANIMATION_DELAY = 300;
+    private final Handler mHideHandler = new Handler();
+    private View mContentView;
+    private final Runnable mHidePart2Runnable = new Runnable() {
+        @SuppressLint("InlinedApi")
+        @Override
+        public void run() {
+            // Delayed removal of status and navigation bar
+
+            // Note that some of these constants are new as of API 16 (Jelly Bean)
+            // and API 19 (KitKat). It is safe to use them, as they are inlined
+            // at compile-time and do nothing on earlier devices.
+            mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+        }
+    };
+    private View mControlsView;
+    private final Runnable mShowPart2Runnable = new Runnable() {
+        @Override
+        public void run() {
+            // Delayed display of UI elements
+            ActionBar actionBar = getSupportActionBar();
+            if (actionBar != null) {
+                actionBar.show();
+            }
+            mControlsView.setVisibility(View.VISIBLE);
+        }
+    };
+    private boolean mVisible;
+    private final Runnable mHideRunnable = new Runnable() {
+        @Override
+        public void run() {
+            hide();
+        }
+    };
+    /**
+     * Touch listener to use for in-layout UI controls to delay hiding the
+     * system UI. This is to prevent the jarring behavior of controls going away
+     * while interacting with activity UI.
+     */
+    private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View view, MotionEvent motionEvent) {
+            if (AUTO_HIDE) {
+                delayedHide(AUTO_HIDE_DELAY_MILLIS);
+            }
+            return false;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_viewer);
+
+        mVisible = true;
+        mControlsView = findViewById(R.id.fullscreen_content_controls);
+        mContentView = findViewById(R.id.fullscreen_content);
+
+
+        // Set up the user interaction to manually show or hide the system UI.
+        mContentView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                toggle();
+            }
+        });
+
+        // Upon interacting with UI controls, delay any scheduled hide()
+        // operations to prevent the jarring behavior of controls going away
+        // while interacting with the UI.
+        findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+
+        // Trigger the initial hide() shortly after the activity has been
+        // created, to briefly hint to the user that UI controls
+        // are available.
+        delayedHide(100);
+    }
+
+    private void toggle() {
+        if (mVisible) {
+            hide();
+        } else {
+            show();
+        }
+    }
+
+    private void hide() {
+        // Hide UI first
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.hide();
+        }
+        mControlsView.setVisibility(View.GONE);
+        mVisible = false;
+
+        // Schedule a runnable to remove the status and navigation bar after a delay
+        mHideHandler.removeCallbacks(mShowPart2Runnable);
+        mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
+    }
+
+    @SuppressLint("InlinedApi")
+    private void show() {
+        // Show the system bar
+        mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+        mVisible = true;
+
+        // Schedule a runnable to display UI elements after a delay
+        mHideHandler.removeCallbacks(mHidePart2Runnable);
+        mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
+    }
+
+    /**
+     * Schedules a call to hide() in [delay] milliseconds, canceling any
+     * previously scheduled calls.
+     */
+    private void delayedHide(int delayMillis) {
+        mHideHandler.removeCallbacks(mHideRunnable);
+        mHideHandler.postDelayed(mHideRunnable, delayMillis);
+    }
+}

+ 44 - 0
app/src/main/java/app/brest/utils/JSONLoader.java

@@ -0,0 +1,44 @@
+package app.brest.utils;
+
+import android.app.Activity;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+/**
+ * Created by ptitcois on 20/08/16.
+ */
+public class JSONLoader {
+    public static JSONObject load(Activity context, String name)
+    {
+        int id = context.getResources().getIdentifier(name,"raw", context.getPackageName());
+        InputStream inputStream = context.getResources().openRawResource(id);
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+        int ctr;
+        try {
+            ctr = inputStream.read();
+            while (ctr != -1) {
+                byteArrayOutputStream.write(ctr);
+                ctr = inputStream.read();
+            }
+            inputStream.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        Log.v("Text Data", byteArrayOutputStream.toString());
+        try {
+            // Parse the data into jsonobject to get original data in form of json.
+            JSONObject jObject = new JSONObject(byteArrayOutputStream.toString());
+            return jObject;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
+

+ 86 - 0
app/src/main/java/app/brest/utils/ResourceManager.java

@@ -0,0 +1,86 @@
+package app.brest.utils;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import app.brest.utils.app.brest.game.Game;
+import app.brest.utils.app.brest.game.Resource;
+import min3d.core.Object3d;
+import min3d.core.Object3dContainer;
+import min3d.parser.IParser;
+import min3d.parser.Parser;
+
+/**
+ * Created by ptitcois on 18/08/16.
+ */
+public class ResourceManager implements Serializable{
+    protected ArrayList<Resource> mAllResources = new ArrayList<Resource>();
+    protected ArrayList< ArrayList<Resource> > mResourcesAquiered  = new ArrayList<ArrayList<Resource> >();
+    protected ArrayList<Resource> mResourcesLeft  = new ArrayList<Resource>();
+
+    public ResourceManager(Game g, Activity act)
+    {
+        mAllResources = g.getAllResources(act);
+    }
+
+    public Resource getResourceLeftByName(String name)
+    {
+        for(int i=0; i<mResourcesLeft.size(); i++)
+        {
+            if(mResourcesLeft.get(i).getName()==name)
+                return mResourcesLeft.get(i);
+        }
+        return null;
+    }
+
+    public void nextStage(int stage)
+    {
+        mResourcesAquiered.add(new ArrayList<Resource>());
+        mResourcesLeft.clear();
+        for(int i=0; i<mAllResources.size(); i++)
+            if(mAllResources.get(i).getStage()==stage)
+                mResourcesLeft.add(mAllResources.get(i));
+    }
+
+    public boolean pickUpResource(String name)
+    {
+        for(int i=0; i<mResourcesLeft.size(); i++)
+        {
+            if(mResourcesLeft.get(i).getName()==name)
+            {
+                mResourcesAquiered.get(mResourcesAquiered.size()-1).add(mResourcesLeft.get(i));
+                mResourcesLeft.remove(i);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ArrayList<Resource> getResourceLeft()
+    {
+        return mResourcesLeft;
+    }
+
+    public ArrayList<Resource> getResourceAquiered(int stage)
+    {
+        return mResourcesAquiered.get(stage-1);
+    }
+
+
+    public ArrayList< ArrayList<Resource> > getResourceAquiered()
+    {
+        return mResourcesAquiered;
+    }
+
+
+    public void deleteAll3DModel()
+    {
+        for(int i=0; i<mAllResources.size(); i++)
+            mAllResources.get(i).delete3DModel();
+    }
+
+}

+ 178 - 0
app/src/main/java/app/brest/utils/SensorsManager.java

@@ -0,0 +1,178 @@
+package app.brest.utils;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v4.app.ActivityCompat;
+
+import app.brest.testmin3d.ARActivity;
+import app.brest.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 18/08/16.
+ */
+public class SensorsManager implements SensorEventListener, LocationListener {
+
+    private SensorManager mSensorManager;
+    private Sensor mAccelerometer;
+    private Sensor mMagnetometer;
+    private float[] mLastAccelerometer = new float[3];
+    private float[] mLastMagnetometer = new float[3];
+    private boolean mLastAccelerometerSet = false;
+    private boolean mLastMagnetometerSet = false;
+    private float[] mR = new float[9];
+    private float[] mOrientation = new float[3];
+    private float mCurrentDegreeX = 0f;
+    private float mCurrentDegreeY = 0f;
+    private float mCurrentDegreeZ = 0f;
+    private SlideBuffer mBufferX;
+    private SlideBuffer mBufferY;
+    private SlideBuffer mBufferZ;
+    private LocationManager mLocationManager;
+
+    private double mLatitude;
+    private double mLongitude;
+    private float mAccuracy;
+    private boolean mIsLocalised = false;
+    private ARActivity mParent;
+
+    public float getAzimuth() {
+        return mBufferX.average();
+    }
+
+
+    public float getAzimuthStep() {
+        return ((int)mBufferX.average()/10)*10;
+    }
+
+    public float getAngleY() {
+        return mBufferY.average();
+    }
+
+    public float getAngleZ() {
+        return mBufferZ.average();
+    }
+
+    public float getOrientation() {
+        return getAzimuth();
+    }
+
+    public GPSPoint getPosition()
+    {
+        if(!mIsLocalised) return null;
+        return new GPSPoint(mLongitude, mLatitude, mAccuracy);
+    }
+
+    public SensorsManager(ARActivity parent ){
+        mParent = parent;
+        mLocationManager = (LocationManager) parent.getSystemService(parent.LOCATION_SERVICE);
+
+        mSensorManager = (SensorManager) parent.getSystemService(parent.SENSOR_SERVICE);
+        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        mBufferX = new SlideBuffer(16);
+        mBufferY = new SlideBuffer(8);
+        mBufferZ = new SlideBuffer(8);
+
+        if (ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+                ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+
+        }
+
+        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
+    }
+
+    public void onResume() {
+        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
+        mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
+
+        if (ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+                ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+
+        }
+
+        if (!mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+            mParent.startActivity(intent);
+        }
+
+        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 400, 1, this);
+        mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 400, 1, this);
+    }
+
+    public void onPause() {
+        mSensorManager.unregisterListener(this, mAccelerometer);
+        mSensorManager.unregisterListener(this, mMagnetometer);
+        if (ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+                && ActivityCompat.checkSelfPermission(mParent, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+        mLocationManager.removeUpdates(this);
+    }
+
+    public void onSensorChanged(SensorEvent event) {
+
+        if (event.sensor == mAccelerometer) {
+            System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length);
+            mLastAccelerometerSet = true;
+        } else if (event.sensor == mMagnetometer) {
+            System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length);
+            mLastMagnetometerSet = true;
+        }
+        if (mLastAccelerometerSet && mLastMagnetometerSet) {
+            SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer);
+            SensorManager.getOrientation(mR, mOrientation);
+            float azimuthInRadians = mOrientation[0];
+            float azimuthInDegress = (float)(Math.toDegrees(azimuthInRadians)+360)%360;
+
+            mCurrentDegreeX = -azimuthInDegress;
+            while(mCurrentDegreeX<0) mCurrentDegreeX+=360;
+            mBufferX.enqueue(mCurrentDegreeX);
+
+            mBufferY.enqueue((float)(Math.toDegrees(mOrientation[1])+360)%360);
+            mBufferZ.enqueue((float)(Math.toDegrees(mOrientation[2])+360)%360);
+        }
+    }
+
+    /*
+     *
+     * GPS
+     *
+     */
+
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int i) {}
+
+
+    @Override
+    public void onLocationChanged(Location location) {
+        mIsLocalised=true;
+        mLongitude=location.getLongitude();
+        mLatitude=location.getLatitude();
+        mAccuracy=location.getAccuracy();
+
+        mParent.setGpsText(mLatitude, mLongitude);
+        //Toast.makeText(mParent,  "Longitude: "+location.getLongitude()+ " Latitude: "+location.getLatitude(), Toast.LENGTH_LONG).show();
+    }
+
+
+
+    @Override
+    public void onStatusChanged(String s, int i, Bundle bundle) {}
+
+    @Override
+    public void onProviderEnabled(String s) {}
+
+    @Override
+    public void onProviderDisabled(String s) {}
+}

+ 80 - 0
app/src/main/java/app/brest/utils/SlideBuffer.java

@@ -0,0 +1,80 @@
+package app.brest.utils;
+
+import java.util.ArrayList;
+
+import app.brest.utils.geometry.Point;
+
+
+public class SlideBuffer {
+    private float mData;
+    private int mSize;
+    private int mHead;
+
+    public SlideBuffer(int size)
+    {
+        mData = 0;
+        mSize = 0;
+        mHead =0;
+    }
+
+    public void enqueue(float val)
+    {
+        mData= mData + 0.5f*(val-mData);
+    }
+
+    public float average()
+    {
+        return mData;
+    }
+
+}
+
+/*
+package app.brest.utils;
+
+import java.util.ArrayList;
+
+public class SlideBuffer {
+    private float[] mData;
+    private float[] mTmp;
+    private int mSize;
+    private int mHead;
+
+    public SlideBuffer(int size)
+    {
+        mData = new float[size];
+        mTmp = new float[size];
+        mSize = 0;
+        mHead =0;
+    }
+
+    public void enqueue(float val)
+    {
+
+        mData[mHead] = val;
+        mHead=(mHead+1)%mData.length;
+        if(mSize<mData.length) mSize++;
+    }
+
+    private float[] exponentialSmoothing( float[] input, float[] output, float alpha ) {
+        if ( output == null )
+            return input;
+        for ( int i=0; i<input.length; i++ ) {
+            output[i] = output[i] + alpha * (input[i] - output[i]);
+        }
+        return output;
+    }
+
+    public float average()
+    {
+
+            float acc=0;
+            for(int i=0; i<mSize; i++)
+                acc+=mData[i];
+            return acc/mSize;
+    }
+
+
+
+}
+*/

+ 139 - 0
app/src/main/java/app/brest/utils/app/brest/game/Area.java

@@ -0,0 +1,139 @@
+package app.brest.utils.app.brest.game;
+
+import android.app.Activity;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import app.brest.utils.SensorsManager;
+import app.brest.utils.geometry.GPSPoint;
+import app.brest.utils.geometry.Shape;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Area  implements Serializable {
+
+    protected String            mName;
+    protected String            mResource;
+    protected int               mStage;
+    protected Shape             mShape = new Shape();
+    protected ArrayList<Place>  mPlaces = new ArrayList<Place>();
+
+
+    public Area(JSONObject root)
+    {
+        try {
+            JSONArray ja = root.getJSONArray("coordinates");
+            for(int i=0; i<ja.length(); i++) {
+                JSONArray jaa = ja.getJSONArray(i);
+                mShape.add(new GPSPoint(jaa.getDouble(0), jaa.getDouble(1)));
+            }
+            mName = root.getString("name");
+            mResource = root.getString("resource");
+            mStage = Integer.parseInt(root.getString("stage"));
+            ja = root.getJSONArray("points");
+            for(int i=0; i<ja.length(); i++) {
+                mPlaces.add(new Place(ja.getJSONObject(i)));
+            }
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getResourceName() {
+        return mResource;
+    }
+
+    public Resource getResource(Activity act) {
+        return new Resource(mResource, mStage, act);
+    }
+
+    public int getStage() {
+        return mStage;
+    }
+
+    public Shape getShape() {
+        return mShape;
+    }
+
+    public ArrayList<Place> getPlaces() {
+        return mPlaces;
+    }
+
+    public int placesCount()
+    {
+        return mPlaces.size();
+    }
+
+    public Place getPlace(int i)
+    {
+        return mPlaces.get(i);
+    }
+
+    public JSONObject getJson()
+    {
+        JSONObject jo = new JSONObject();
+        JSONArray ja = new JSONArray();
+
+        try {
+            for(int i=0; i<mPlaces.size(); i++)
+                ja.put(mPlaces.get(i).getJson());
+            jo.put("points", ja);
+            jo.put("coordinates", mShape.getJson());
+            jo.put("name", mName);
+            jo.put("resource", mResource);
+            jo.put("stage", ""+mStage);
+        }catch( Exception e)
+        {
+            e.printStackTrace();
+        }
+        return jo;
+    }
+
+    public boolean isOnArea(Player p)
+    {
+        GPSPoint player = p.getPosition();
+        if(player == null)
+            return false;
+
+        return mShape.contains(player);
+
+    }
+
+
+    public boolean isOnPlace(Player p)
+    {
+        for(int i=0; i<mPlaces.size(); i++)
+            if(mPlaces.get(i).isPlayerOn(p))
+                return true;
+        return false;
+    }
+
+    public boolean isOnDirection(Player p)
+    {
+
+        for(int i=0; i<mPlaces.size(); i++)
+            if(mPlaces.get(i).isOnDirection(p))
+                return true;
+        return false;
+    }
+
+    public double getDistanceToNextPlace(Player p)
+    {
+        double min = 32*1000*1000*1000;
+        for(int i=0; i<mPlaces.size(); i++)
+            if(mPlaces.get(i).getDistance(p)<min)
+                min=mPlaces.get(i).getDistance(p);
+        return min;
+    }
+}

+ 294 - 0
app/src/main/java/app/brest/utils/app/brest/game/Game.java

@@ -0,0 +1,294 @@
+package app.brest.utils.app.brest.game;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import app.brest.testmin3d.ARActivity;
+import app.brest.utils.JSONLoader;
+import app.brest.utils.ResourceManager;
+import app.brest.utils.SensorsManager;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Game  implements Serializable {
+
+    protected ArrayList<Area> mAreas = new ArrayList<Area>();
+    protected ArrayList<Area> mCurrentStageAreas = new ArrayList<Area>();
+    protected int             mNStages;
+    protected int             mCurrentStage=0;
+    protected Player          mPlayer;
+    protected ResourceManager mResources;
+
+    public Game(String name, ARActivity act)
+    {
+        JSONObject root = JSONLoader.load(act, name);
+
+        mPlayer = new Player(act);
+        try {
+            JSONArray areas = root.getJSONArray("areas");
+            for(int i=0; i<areas.length(); i++)
+                mAreas.add(new Area(areas.getJSONObject(i)));
+            mResources = new ResourceManager(this, act);
+            findMaxStage();
+            nextStage();
+
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    public int getNStages() {
+        return mNStages;
+    }
+
+    public int getCurrentStage() {
+        return mCurrentStage;
+    }
+
+    public void findMaxStage()
+    {
+        mNStages=0;
+        for(int i=0; i<mAreas.size(); i++) {
+            if (mAreas.get(i).getStage() > mNStages)
+                mNStages = mAreas.get(i).getStage();
+        }
+    }
+
+    public boolean nextStage()
+    {
+        mCurrentStage++;
+        if(mCurrentStage>mNStages) return true;
+
+        loadStageAreasResources();
+        mResources.nextStage(mCurrentStage);
+
+        return false;
+    }
+
+    public void loadStageAreasResources()
+    {
+        mCurrentStageAreas.clear();
+        for(int i=0; i<mAreas.size(); i++)
+        {
+            if(mAreas.get(i).getStage() == mCurrentStage)
+                mCurrentStageAreas.add(mAreas.get(i));
+        }
+    }
+
+    public int currentAreasCount()
+    {
+        return mCurrentStageAreas.size();
+    }
+
+    public Area getAreaStage(int i)
+    {
+        return mCurrentStageAreas.get(i);
+    }
+
+    public void removeArea(String name)
+    {
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+            if(mCurrentStageAreas.get(i).getName() == name)
+                mCurrentStageAreas.remove(i);
+    }
+
+    private void removeArea(int index)
+    {
+        mCurrentStageAreas.remove(index);
+    }
+
+    public boolean pickResource()
+    {
+        ArrayList<Resource> r = getResourcesNextToPlayer();
+        for(int i=0; i<r.size(); i++) {
+            for (int j = 0; j < mCurrentStageAreas.size(); j++)
+                if (r.get(i).getName() == mCurrentStageAreas.get(j).getName()) {
+                    removeArea(j);
+                }
+            mResources.pickUpResource(r.get(i).getName());
+        }
+        return mCurrentStageAreas.size()==0;
+    }
+
+
+
+    public String toString()
+    {
+        String out = "mAreas[]:\n";
+        for(int i=0; i<mAreas.size(); i++)
+            out+="\tmAres["+i+"] = "+mAreas.get(i).getJson().toString()+"\n";
+        out+="\nmResourcesAcquired[] : \n";
+        out+="mNStages :" +mNStages +"\n";
+        out+="mCurrentStage" + mCurrentStage +"\n";
+        return out;
+    }
+
+    public boolean save(Activity context)
+    {
+        try {
+            FileOutputStream fos = context.openFileOutput("data", Context.MODE_PRIVATE);
+            ObjectOutputStream os = new ObjectOutputStream(fos);
+                os.writeObject(this);
+            os.close();
+            fos.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Log.e("SAVING", "Save failed:"+e.getMessage());
+            return false;
+        }
+        Log.e("SAVING", "Save OK");
+        return true;
+    }
+
+    public static Game load(Activity context)
+    {
+        try {
+            FileInputStream fis = context.openFileInput("data");
+            ObjectInputStream is = new ObjectInputStream(fis);
+            Game obj = (Game) is.readObject();
+            is.close();
+            fis.close();
+            return obj;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    public ArrayList<Resource> getAllResources(Activity act)
+    {
+        ArrayList<Resource> tmp = new ArrayList<Resource>();
+        for(int i=0; i<mAreas.size(); i++)
+            if(!tmp.contains(mAreas.get(i)))
+                tmp.add(mAreas.get(i).getResource(act));
+        return tmp;
+    }
+
+    public static boolean isSaved(Activity context)
+    {
+        boolean b = false;
+        try {
+            FileInputStream fis = context.openFileInput("data");
+            if(fis!=null)
+                b=true;
+            fis.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return b;
+    }
+
+    public void onPause(Activity context)
+    {
+        save(context);
+        mPlayer.onPause();
+        removeCached3DModels();
+    }
+
+
+    public void onResume(ARActivity context)
+    {
+        mPlayer.onResume(context);
+    }
+
+    public void setSensorsManager(SensorsManager sm)
+    {
+        mPlayer.setSensorsManager(sm);
+    }
+
+    /**
+     * Charge la ressource 3d quand on entre dans une zone
+     */
+    public boolean precache3DResource(Activity contecxt)
+    {
+        boolean b =false;
+        for(int i=0; i<mAreas.size(); i++)
+        {
+            if(mAreas.get(i).getStage()==mCurrentStage &&  mAreas.get(i).isOnArea(mPlayer))
+            {
+                b=true;
+                Resource r = mResources.getResourceLeftByName(mAreas.get(i).getResourceName());
+                if(r!=null)
+                    r.get3DModel(contecxt);
+            }
+        }
+        return b;
+    }
+
+
+    /*
+     *
+     * Fonction principale de positionnnemnt et orientation
+     *
+     */
+    public ArrayList<Resource> getResourcesNextToPlayer()
+    {
+        ArrayList<Resource> r = new ArrayList<Resource>();
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            boolean b = mCurrentStageAreas.get(i).isOnArea(mPlayer);
+            if(b)
+            {
+
+                if(mCurrentStageAreas.get(i).isOnDirection(mPlayer))
+                {
+                    r.add(mResources.getResourceLeftByName(mCurrentStageAreas.get(i).getResourceName()));
+                }
+            }
+        }
+        return r;
+    }
+    public String getResourcesNextToPlayer2()
+    {
+        String str = "Detected\n";
+        ArrayList<Resource> r = new ArrayList<Resource>();
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            if(mCurrentStageAreas.get(i).isOnArea(mPlayer))
+            {
+
+                str+="\t" +mCurrentStageAreas.get(i).getResourceName()+" "+mCurrentStageAreas.get(i).getDistanceToNextPlace(mPlayer)+" m\n";
+
+            }
+        }
+        return str;
+    }
+
+
+
+    public Player getPlayer()
+    {
+        return mPlayer;
+    }
+
+    public void removeCached3DModels()
+    {
+        mResources.deleteAll3DModel();
+    }
+
+    public void newSensorManager(ARActivity act)
+    {
+        mPlayer.newSensorManager(act);
+    }
+
+
+}

+ 106 - 0
app/src/main/java/app/brest/utils/app/brest/game/Place.java

@@ -0,0 +1,106 @@
+package app.brest.utils.app.brest.game;
+
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import app.brest.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Place  implements Serializable {
+    public Place(JSONObject root)
+    {
+
+        try {
+            JSONArray arr = root.getJSONArray("coordinates");
+            mLocation = new GPSPoint(arr.getDouble(0), arr.getDouble(1));
+            mRadius = Double.parseDouble(root.getString("radius"));
+            mField = Double.parseDouble(root.getString("field"));
+            mAngle = Double.parseDouble(root.getString("angle"));
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    protected GPSPoint  mLocation;
+    protected double    mRadius;
+    protected double    mField;
+    protected double    mAngle;
+
+    public GPSPoint getLocation() {
+        return mLocation;
+    }
+
+    public double getRadius() {
+        return mRadius;
+    }
+
+    public double getField() {
+        return mField;
+    }
+
+    public double getAngle() {
+        return mAngle;
+    }
+
+    public JSONObject getJson()
+    {
+        JSONObject js = new JSONObject();
+        JSONArray ja = new JSONArray();
+        try {
+            ja.put(mLocation.getY());
+            ja.put(mLocation.getY());
+            js.put("coordinates", ja);
+            js.put("radius", ""+mRadius);
+            js.put("field", ""+mField);
+            js.put("angle", ""+mAngle);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return js;
+    }
+
+    public boolean isPlayerOn(Player p)
+    {
+        GPSPoint pp = p.getPosition();
+        if(pp==null) return false;
+
+        return mLocation.getDistanceWith(pp)<=mRadius;
+    }
+
+    public boolean isOnDirection(Player p)
+    {
+        boolean ret;
+        float angle = p.getOrientation()%360;
+        while(angle<0) angle+=360;
+        double min = mAngle-mField/2;
+        double max = mAngle+mField/2;
+
+
+        if(min<0)
+        {
+            min+=360;
+            ret=angle>=min  || angle <=max;
+        }
+        else if(max>=360)
+        {
+            max-=360;
+            ret=angle<=max || (angle>=min);
+        }else
+        {
+            ret=(angle<=max && angle>=min);
+        }
+        return ret;
+    }
+
+    public double getDistance(Player p)
+    {
+        return mLocation.getDistanceWith(p.getPosition());
+    }
+}

+ 71 - 0
app/src/main/java/app/brest/utils/app/brest/game/Player.java

@@ -0,0 +1,71 @@
+package app.brest.utils.app.brest.game;
+
+import java.io.Serializable;
+
+import app.brest.testmin3d.ARActivity;
+import app.brest.utils.SensorsManager;
+import app.brest.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 20/08/16.
+ */
+public class Player implements Serializable{
+    protected transient SensorsManager mSensors;
+
+    public Player(ARActivity sm)
+    {
+        mSensors=new SensorsManager(sm);
+    }
+
+    public void setSensorsManager(SensorsManager sm)
+    {
+        mSensors=sm;
+    }
+
+    public SensorsManager getSensorsManager()
+    {
+        return mSensors;
+    }
+
+    public void onPause()
+    {
+        mSensors.onPause();
+    }
+
+
+    public void onResume(ARActivity act)
+    {
+        mSensors.onResume();
+    }
+
+    public GPSPoint getPosition()
+    {
+        return mSensors.getPosition();
+    }
+
+    public float getOrientation()
+    {
+        return mSensors.getOrientation();
+    }
+
+    public float getOrientationX()
+    {
+        return mSensors.getAzimuth();
+    }
+
+    public float getOrientationY()
+    {
+        return mSensors.getAngleY();
+    }
+
+
+    public float getOrientationZ()
+    {
+        return mSensors.getAngleZ();
+    }
+
+    public void newSensorManager(ARActivity act)
+    {
+        mSensors = new SensorsManager(act);
+    }
+}

+ 131 - 0
app/src/main/java/app/brest/utils/app/brest/game/Resource.java

@@ -0,0 +1,131 @@
+package app.brest.utils.app.brest.game;
+
+import android.app.Activity;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import app.brest.utils.JSONLoader;
+import min3d.core.Object3d;
+import min3d.parser.IParser;
+import min3d.parser.Parser;
+import min3d.vos.Number3d;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Resource implements Serializable {
+
+    protected String mName;
+    protected int    mStage;
+    protected transient Object3d  m3DModel = null;
+    protected Number3d mPosition = new Number3d(0,0,0);
+    protected Number3d mRotation = new Number3d(0,0,0);
+    protected Number3d mScale = new Number3d(1,1,1);
+    protected String mComment = "null";
+    protected String mModelPath = "";
+    protected boolean isLoaded = false;
+
+    protected void loadResourceData(Activity act)
+    {
+        JSONObject obj = JSONLoader.load(act, mName+"_res");
+        try {
+            mComment=obj.getString("comment");
+            mPosition.x =(float) obj.getJSONArray("position").getDouble(0);
+            mPosition.y =(float) obj.getJSONArray("position").getDouble(1);
+            mPosition.z =(float) obj.getJSONArray("position").getDouble(2);
+
+
+            mRotation.x =(float) obj.getJSONArray("rotation").getDouble(0);
+            mRotation.y =(float) obj.getJSONArray("rotation").getDouble(1);
+            mRotation.z =(float) obj.getJSONArray("rotation").getDouble(2);
+
+
+            mScale.x =(float) obj.getJSONArray("scale").getDouble(0);
+            mScale.y =(float) obj.getJSONArray("scale").getDouble(1);
+            mScale.z =(float) obj.getJSONArray("scale").getDouble(2);
+
+
+            mModelPath = obj.getString("model");
+            isLoaded=true;
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Resource(String name, int stage, Activity act)
+    {
+        mName=name;
+        mStage=stage;
+        loadResourceData(act);
+    }
+
+    public Resource(JSONObject obj, Activity act)
+    {
+        try {
+            mName=obj.getString("name");
+            mStage=obj.getInt("stage");
+            loadResourceData(act);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public int getStage() {
+        return mStage;
+    }
+
+    public  JSONObject getJson()
+    {
+        JSONObject jo = new JSONObject();
+        try {
+            jo.put("name", mName);
+            jo.put("stage", mStage);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return jo;
+    }
+
+    public Object3d get3DModel(Activity context)
+    {
+        if(m3DModel==null)
+        {
+            IParser myParser = Parser.createParser(Parser.Type.OBJ, context.getResources(), "app.brest.testmin3d:raw/"+mName+"_obj",true);
+            myParser.parse();
+
+            m3DModel = myParser.getParsedObject();
+            m3DModel.position().x = mPosition.x; m3DModel.position().y = mPosition.y; m3DModel.position().z = mPosition.z;
+            m3DModel.rotation().x = mRotation.x; m3DModel.rotation().y = mRotation.y; m3DModel.rotation().z = mRotation.z;
+            m3DModel.scale().x = mScale.x; m3DModel.scale().y = mScale.y; m3DModel.scale().z = mScale.z;
+            Log.e("MODEL", mName+" ("+m3DModel.scale().x+", "+m3DModel.scale().y+", "+m3DModel.scale().z+")");
+        }
+        return m3DModel;
+
+    }
+
+
+
+    public void delete3DModel()
+    {
+        m3DModel = null;
+    }
+
+    public boolean is3DModelLoaded()
+    {
+        return m3DModel!=null;
+    }
+
+    public void scale3DModel(float factor)
+    {
+        if(m3DModel!=null)
+            m3DModel.scale().x = m3DModel.scale().y = m3DModel.scale().z = factor;
+    }
+}

+ 85 - 0
app/src/main/java/app/brest/utils/geometry/GPSPoint.java

@@ -0,0 +1,85 @@
+package app.brest.utils.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Created by ptitcois on 16/08/16.
+ */
+public class GPSPoint extends app.brest.utils.geometry.Point  implements Serializable {
+    protected float mAccuracy;
+
+
+
+    public GPSPoint()
+    {
+        mX=mY=0.0;
+    }
+
+    public GPSPoint(Point b)
+    {
+        mX=b.mX;
+        mY=b.mY;
+    }
+
+    public GPSPoint(double x, double y)
+    {
+        mX=x;
+        mY=y;
+    }
+
+    public GPSPoint(double x, double y, float acc)
+    {
+        mX=x;
+        mY=y;
+        mAccuracy=acc;
+    }
+
+    public float getAccuracy()
+    {
+        return mAccuracy;
+    }
+
+    public void setAccuracy(float acc)
+    {
+        mAccuracy=acc;
+    }
+
+    public double getDistanceWith(Point b)
+    {
+		double theta = mY - b.mY;
+		double dist = Math.sin(deg2rad(mX)) * Math.sin(deg2rad(b.mX)) + Math.cos(deg2rad(mX)) * Math.cos(deg2rad(b.mX)) * Math.cos(deg2rad(theta));
+		dist = Math.acos(dist);
+		dist = rad2deg(dist);
+		dist = dist * 60 * 1.1515;
+		dist = dist * 1.609344 *1000;
+
+		return (dist);
+
+	}
+    
+    private static double rad2deg(double rad) {
+		return (rad * 180 / Math.PI);
+	}
+    
+    private static double deg2rad(double deg) {
+		return (deg * Math.PI / 180.0);
+	}
+
+    public double getDistanceXWith(GPSPoint _b)
+    {
+        Point b = new GPSPoint(_b);
+        b.mY=mY;
+
+        return getDistanceWith(b);
+    }
+
+
+    public double getDistanceYWith(GPSPoint _b)
+    {
+        Point b = new GPSPoint(_b);
+        b.mX=mX;
+
+        return getDistanceWith(b);
+    }
+
+}

+ 98 - 0
app/src/main/java/app/brest/utils/geometry/Point.java

@@ -0,0 +1,98 @@
+package app.brest.utils.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Created by ptitcois on 16/08/16.
+ */
+
+
+public class Point  implements Serializable {
+    protected  double mX;
+    protected  double mY;
+
+    public Point()
+    {
+        mX=mY=0.0;
+    }
+
+    public Point(Point b)
+    {
+        mX=b.mX;
+        mY=b.mY;
+    }
+
+    public Point(double x, double y)
+    {
+        mX=x;
+        mY=y;
+    }
+
+
+    void setX(double x)
+    {
+        mX=x;
+    }
+
+    void setY(double y)
+    {
+        mY=y;
+    }
+
+    void set(double x, double y)
+    {
+        mX=x;
+        mY=y;
+    }
+
+    public double getDistanceWith(Point b)
+    {
+        double dx = b.mX - mX;
+        double dy = b.mY - mY;
+
+        if(dx<0) dx=-dx;
+        if(dy<0) dy=-dy;
+
+        return Math.sqrt(dx*dx + dy*dy);
+    }
+
+    public String toString()
+    {
+    	return "("+mX+", "+mY+")";
+    }
+    
+    public double getAngleRadWith(Point b)
+    {
+        double dx = b.mX - mX;
+        double dy = b.mY - mY;
+
+        return Math.atan(dx/dy);
+    }
+
+    public double getAngleDegWith(Point b)
+    {
+        return getAngleRadWith(b) *180/Math.PI;
+    }
+
+
+    public double getX(){return mX;}
+    public double getY(){return mY;}
+
+    public double getDistanceXWith(Point _b)
+    {
+        Point b = new Point(_b);
+        b.mY=mY;
+
+        return getDistanceWith(b);
+    }
+
+
+    public double getDistanceYWith(Point _b)
+    {
+        Point b = new Point(_b);
+        b.mX=mX;
+
+        return getDistanceWith(b);
+    }
+
+}

+ 120 - 0
app/src/main/java/app/brest/utils/geometry/Shape.java

@@ -0,0 +1,120 @@
+package app.brest.utils.geometry;
+/**
+ * Created by François Gautrais on 16/08/16.
+ * Support also GPS Shape (By using GPSPoint)
+ */
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class Shape  implements Serializable {
+    protected ArrayList<Point> mPoints = new ArrayList<Point>();
+
+    public Shape()
+    {
+
+    }
+
+    public Shape(Point a)
+    {
+        mPoints.add(a);
+    }
+
+    public Shape(Point a, Point b)
+    {
+        mPoints.add(a);
+        mPoints.add(b);
+    }
+
+    public Shape(Point a, Point b, Point c)
+    {
+        mPoints.add(a);
+        mPoints.add(b);
+        mPoints.add(c);
+    }
+    
+    public void add(Point p)
+    {
+    	mPoints.add(p);
+    }
+
+    public boolean contains(Point p)
+    {
+        int nok=0;
+        int nerr=0;
+
+        for(int i=0; i<mPoints.size(); i++)
+        {
+            boolean ok=false;
+            for(int j=0; j<mPoints.size()-2; j++)
+            {
+                int k=(j+1+i)%mPoints.size();
+                int l=(j+2+i)%mPoints.size();
+                ok=isInTriangle(mPoints.get(i), mPoints.get(k), mPoints.get(l), p);
+                if(ok) break;
+            }
+            if(ok) nok++;
+            else nerr++;
+        }
+        return (nok>0);
+    }
+
+
+    
+    static boolean  moreOrLess(double a, double b)
+    {
+    	double marge = a>b?a/1000:b/1000;
+    	double res = a - b;
+    	if(res<0) res=-res;
+    	return res<marge;
+    }
+    
+    
+
+    public static boolean isInTriangle(Point a, Point b, Point c, Point p)
+    {
+    		double A = getTriangleSurface (a,b,c);
+    	 
+    	   /* Calculate area of triangle PBC */  
+    	   double A1 = getTriangleSurface (p,b, c);
+    	   /* Calculate area of triangle PAC */  
+    	   double A2 = getTriangleSurface (a, p, c);
+    	 
+    	   /* Calculate area of triangle PAB */   
+    	   double A3 = getTriangleSurface (a, b, p);
+
+    	   return moreOrLess(A ,A1 + A2 + A3);
+    }
+    public static double getTriangleSurface(Point a, Point b, Point c)
+    {
+        double A,B,C,p;
+        A=a.getDistanceWith(b);
+        B=b.getDistanceWith(c);
+        C=c.getDistanceWith(a);
+        p=(A+B+C)/2;
+
+        return Math.sqrt(p*(p-A)*(p-B)*(p-C));
+    }
+
+    public JSONArray getJson()
+    {
+        JSONArray ar = new JSONArray();
+        try {
+            for(int i=0; i<mPoints.size(); i++)
+            {
+                JSONArray arr = new JSONArray();
+                arr.put(mPoints.get(i).getY());
+                arr.put(mPoints.get(i).getX());
+                ar.put(arr);
+            }
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return ar;
+    }
+}

+ 17 - 0
app/src/main/java/min3d/Min3d.java

@@ -0,0 +1,17 @@
+package min3d;
+
+public class Min3d 
+{
+	public static final String TAG = "Min3D";
+	
+	/*
+	 * Project homepage: 	http://code.google.com/p/min3d
+	 * License:				MIT
+	 * 
+	 * Author: 				Lee Felarca
+	 * Website:				http://www.zeropointnine.com/blog
+	 *
+	 * Author: 				Dennis Ippel
+	 * Author blog:			http://www.rozengain.com/blog/
+	 */
+}

+ 46 - 0
app/src/main/java/min3d/Shared.java

@@ -0,0 +1,46 @@
+package min3d;
+
+import min3d.core.TextureManager;
+import min3d.core.Renderer;
+import android.content.Context;
+
+/**
+ * Holds static references to TextureManager, Renderer, and the application Context. 
+ */
+public class Shared 
+{
+	private static Context _context;
+	private static Renderer _renderer;
+	private static TextureManager _textureManager;
+
+	
+	public static Context context()
+	{
+		return _context;
+	}
+	public static void context(Context $c)
+	{
+		_context = $c;
+	}
+
+	public static Renderer renderer()
+	{
+		return _renderer;
+	}
+	public static void renderer(Renderer $r)
+	{
+		_renderer = $r;
+	}
+	
+	/**
+	 * You must access the TextureManager instance through this accessor
+	 */
+	public static TextureManager textureManager()
+	{
+		return _textureManager;
+	}
+	public static void textureManager(TextureManager $bm)
+	{
+		_textureManager = $bm;
+	}
+}

+ 84 - 0
app/src/main/java/min3d/Utils.java

@@ -0,0 +1,84 @@
+package min3d;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import min3d.core.Object3d;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+
+public class Utils 
+{
+	public static final float DEG = (float)(Math.PI / 180f);
+		
+	private static final int BYTES_PER_FLOAT = 4;  
+	
+	/**
+	 * Convenience method to create a Bitmap given a Context's drawable resource ID. 
+	 */
+	public static Bitmap makeBitmapFromResourceId(Context $context, int $id)
+	{
+		InputStream is = $context.getResources().openRawResource($id);
+		
+		Bitmap bitmap;
+		try {
+		   bitmap = BitmapFactory.decodeStream(is);
+		} finally {
+		   try {
+		      is.close();
+		   } catch(IOException e) {
+		      // Ignore.
+		   }
+		}
+	      
+		return bitmap;
+	}
+	
+	/**
+	 * Convenience method to create a Bitmap given a drawable resource ID from the application Context. 
+	 */
+	public static Bitmap makeBitmapFromResourceId(int $id)
+	{
+		return makeBitmapFromResourceId(Shared.context(), $id);
+	}
+
+
+	/**
+	 * Add two triangles to the Object3d's faces using the supplied indices
+	 */
+	public static void addQuad(Object3d $o, int $upperLeft, int $upperRight, int $lowerRight, int $lowerLeft)
+	{
+		$o.faces().add((short)$upperLeft, (short)$lowerRight, (short)$upperRight);
+		$o.faces().add((short)$upperLeft, (short)$lowerLeft, (short)$lowerRight);
+	}
+	
+	public static FloatBuffer makeFloatBuffer3(float $a, float $b, float $c)
+	{
+		ByteBuffer b = ByteBuffer.allocateDirect(3 * BYTES_PER_FLOAT);
+		b.order(ByteOrder.nativeOrder());
+		FloatBuffer buffer = b.asFloatBuffer();
+		buffer.put($a);
+		buffer.put($b);
+		buffer.put($c);
+		buffer.position(0);
+		return buffer;
+	}
+
+	public static FloatBuffer makeFloatBuffer4(float $a, float $b, float $c, float $d)
+	{
+		ByteBuffer b = ByteBuffer.allocateDirect(4 * BYTES_PER_FLOAT);
+		b.order(ByteOrder.nativeOrder());
+		FloatBuffer buffer = b.asFloatBuffer();
+		buffer.put($a);
+		buffer.put($b);
+		buffer.put($c);
+		buffer.put($d);
+		buffer.position(0);
+		return buffer;
+	}
+}

+ 187 - 0
app/src/main/java/min3d/animation/AnimationObject3d.java

@@ -0,0 +1,187 @@
+package min3d.animation;
+
+import min3d.core.FacesBufferedList;
+import min3d.core.Object3d;
+import min3d.core.TextureList;
+import min3d.core.Vertices;
+
+public class AnimationObject3d extends Object3d {
+	private int numFrames;
+	private KeyFrame[] frames;
+	private int currentFrameIndex;
+	private long startTime;
+	private long currentTime;
+	private boolean isPlaying;
+	private float interpolation;
+	private float fps = 70;
+	private boolean updateVertices = true;	
+	private String currentFrameName;
+	private int loopStartIndex;
+	private boolean loop = false;
+
+	public AnimationObject3d(int $maxVertices, int $maxFaces, int $numFrames) {
+		super($maxVertices, $maxFaces);
+		this.numFrames = $numFrames;
+		this.frames = new KeyFrame[numFrames];
+		this.currentFrameIndex = 0;
+		this.isPlaying = false;
+		this.interpolation = 0;
+		this._animationEnabled = true;
+	}
+	
+	public AnimationObject3d(Vertices $vertices, FacesBufferedList $faces, TextureList $textures, KeyFrame[] $frames)
+	{
+		super($vertices, $faces, $textures);
+		numFrames = $frames.length;
+		frames = $frames;
+	}
+
+	public int getCurrentFrame() {
+		return currentFrameIndex;
+	}
+
+	public void addFrame(KeyFrame frame) {
+		frames[currentFrameIndex++] = frame;
+	}
+
+	public void setFrames(KeyFrame[] frames) {
+		this.frames = frames;
+	}
+
+	public void play() {
+		startTime = System.currentTimeMillis();
+		isPlaying = true;
+		currentFrameName = null;
+		loop = false;
+	}
+
+	public void play(String name) {
+		currentFrameIndex = 0;
+		currentFrameName = name;
+
+		for (int i = 0; i < numFrames; i++) {
+			if (frames[i].getName().equals(name))
+			{
+				loopStartIndex = currentFrameIndex = i;
+				break;
+			}
+		}
+
+		startTime = System.currentTimeMillis();
+		isPlaying = true;
+	}
+	
+	public void play(String name, boolean loop) {
+		this.loop = loop;
+		play(name);
+	}
+
+	public void stop() {
+		isPlaying = false;
+		currentFrameIndex = 0;
+	}
+
+	public void pause() {
+		isPlaying = false;
+	}
+
+	public void update() {
+		if (!isPlaying || !updateVertices)
+			return;
+		currentTime = System.currentTimeMillis();
+		KeyFrame currentFrame = frames[currentFrameIndex];
+		KeyFrame nextFrame = frames[(currentFrameIndex + 1) % numFrames];
+		
+		if(currentFrameName != null && !currentFrameName.equals(currentFrame.getName()))
+		{
+			if(!loop)
+				stop();
+			else
+				currentFrameIndex = loopStartIndex;
+			return;
+		}
+		
+		float[] currentVerts = currentFrame.getVertices();
+		float[] nextVerts = nextFrame.getVertices();
+		float[] currentNormals = currentFrame.getNormals();
+		float[] nextNormals = nextFrame.getNormals();
+		int numVerts = currentVerts.length;
+		
+		float[] interPolatedVerts = new float[numVerts];
+		float[] interPolatedNormals = new float[numVerts];
+
+		for (int i = 0; i < numVerts; i += 3) {
+			interPolatedVerts[i] = currentVerts[i] + interpolation * (nextVerts[i] - currentVerts[i]);
+			interPolatedVerts[i + 1] = currentVerts[i + 1] + interpolation * (nextVerts[i + 1] - currentVerts[i + 1]);
+			interPolatedVerts[i + 2] = currentVerts[i + 2] + interpolation 	* (nextVerts[i + 2] - currentVerts[i + 2]);
+			interPolatedNormals[i] = currentNormals[i] + interpolation * (nextNormals[i] - currentNormals[i]);
+			interPolatedNormals[i + 1] = currentNormals[i + 1] + interpolation * (nextNormals[i + 1] - currentNormals[i + 1]);
+			interPolatedNormals[i + 2] = currentNormals[i + 2] + interpolation * (nextNormals[i + 2] - currentNormals[i + 2]);
+		}
+
+		interpolation += fps * (currentTime - startTime) / 1000;
+		
+		vertices().overwriteNormals(interPolatedNormals);
+		vertices().overwriteVerts(interPolatedVerts);
+	
+		if (interpolation > 1) {
+			interpolation = 0;
+			currentFrameIndex++;
+
+			if (currentFrameIndex >= numFrames)
+				currentFrameIndex = 0;
+		}
+		
+		startTime = System.currentTimeMillis();
+	}
+
+	public float getFps() {
+		return fps;
+	}
+
+	public void setFps(float fps) {
+		this.fps = fps;
+	}
+	
+	public Object3d clone(boolean cloneData)
+	{
+		Vertices v = cloneData ? _vertices.clone() : _vertices;
+		FacesBufferedList f = cloneData ? _faces.clone() : _faces;
+		//KeyFrame[] fr = cloneData ? getClonedFrames() : frames;
+		
+		AnimationObject3d clone = new AnimationObject3d(v, f, _textures, frames);
+		clone.position().x = position().x;
+		clone.position().y = position().y;
+		clone.position().z = position().z;
+		clone.rotation().x = rotation().x;
+		clone.rotation().y = rotation().y;
+		clone.rotation().z = rotation().z;
+		clone.scale().x = scale().x;
+		clone.scale().y = scale().y;
+		clone.scale().z = scale().z;
+		clone.setFps(fps);
+		clone.animationEnabled(animationEnabled());
+		return clone;
+	}
+	
+	public KeyFrame[] getClonedFrames()
+	{
+		int len = frames.length;
+		KeyFrame[] cl = new KeyFrame[len];
+		
+		for(int i=0; i<len; i++)
+		{
+			cl[i] = frames[i].clone();
+		}
+		
+		return cl;
+	}
+
+	public boolean getUpdateVertices() {
+		return updateVertices;
+	}
+
+	public void setUpdateVertices(boolean updateVertices) {
+		this.updateVertices = updateVertices;
+	}
+}

+ 103 - 0
app/src/main/java/min3d/animation/KeyFrame.java

@@ -0,0 +1,103 @@
+package min3d.animation;
+
+import min3d.vos.Number3d;
+
+public class KeyFrame {
+	private String name;
+	private float[] vertices;
+	private float[] normals;
+
+	private int[] indices;
+	
+	public KeyFrame(String name, float[] vertices)
+	{
+		this.name = name;
+		this.vertices = vertices;
+	}
+
+	public KeyFrame(String name, float[] vertices, float[] normals)
+	{
+		this(name, vertices);
+		this.normals = normals;
+	}
+	
+	public String getName() {
+		return name;
+	}
+
+	public float[] getVertices() {
+		return vertices;
+	}
+
+	public int[] getIndices() {
+		return indices;
+	}
+
+	public float[] getNormals() {
+		return normals;
+	}
+	
+	public void setIndices(int[] indices) {
+		this.indices = indices;
+		float[] compressed = vertices;
+		vertices = new float[indices.length*3];
+		int len = indices.length;
+		int vi = 0;
+		int ii = 0;
+		int normalIndex = 0;
+		
+		for(int i=0; i<len; i++)
+		{
+			ii = indices[i] * 3;
+			vertices[vi++] = compressed[ii];
+			vertices[vi++] = compressed[ii + 1];
+			vertices[vi++] = compressed[ii + 2];
+		}
+		
+		normals = new float[vertices.length];
+		int vertLen = vertices.length;
+		
+		for(int i=0; i<vertLen; i+=9)
+		{
+			Number3d normal = calculateFaceNormal(
+					new Number3d(vertices[i], vertices[i+1], vertices[i+2]),
+					new Number3d(vertices[i+3], vertices[i+4], vertices[i+5]),
+					new Number3d(vertices[i+6], vertices[i+7], vertices[i+8])
+					);
+			normals[normalIndex++] = normal.x;
+			normals[normalIndex++] = normal.y;
+			normals[normalIndex++] = normal.z;
+			normals[normalIndex++] = normal.x;
+			normals[normalIndex++] = normal.y;
+			normals[normalIndex++] = normal.z;
+			normals[normalIndex++] = normal.x;
+			normals[normalIndex++] = normal.y;
+			normals[normalIndex++] = normal.z;
+		}
+	}
+	
+	public Number3d calculateFaceNormal(Number3d v1, Number3d v2, Number3d v3)
+	{
+		Number3d vector1 = Number3d.subtract(v2, v1);
+		Number3d vector2 = Number3d.subtract(v3, v1);
+		
+		Number3d normal = new Number3d();
+		normal.x = (vector1.y * vector2.z) - (vector1.z * vector2.y);
+		normal.y = -((vector2.z * vector1.x) - (vector2.x * vector1.z));
+		normal.z = (vector1.x * vector2.y) - (vector1.y * vector2.x);
+		
+		double normFactor = Math.sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z));
+		
+		normal.x /= normFactor;
+		normal.y /= normFactor;
+		normal.z /= normFactor;
+		
+		return normal;
+	}
+	
+	public KeyFrame clone()
+	{
+		KeyFrame k = new KeyFrame(name, vertices.clone(), normals.clone());
+		return k;
+	}
+}

+ 160 - 0
app/src/main/java/min3d/core/Color4BufferList.java

@@ -0,0 +1,160 @@
+package min3d.core;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import min3d.vos.Color4;
+
+
+public class Color4BufferList
+{
+	public static final int PROPERTIES_PER_ELEMENT = 4;
+	public static final int BYTES_PER_PROPERTY = 1;
+
+	private ByteBuffer _b;
+	private int _numElements;
+	
+	public Color4BufferList(ByteBuffer $b, int $size)
+	{
+		_b = ByteBuffer.allocate($b.limit() * BYTES_PER_PROPERTY);
+		_b.put($b);
+		_numElements = $size;
+	}
+	
+	public Color4BufferList(int $maxElements)
+	{
+		int numBytes = $maxElements * PROPERTIES_PER_ELEMENT * BYTES_PER_PROPERTY;
+		_b = ByteBuffer.allocateDirect(numBytes); 
+		_b.order(ByteOrder.nativeOrder());
+	}
+	
+	/**
+	 * The number of items in the list. 
+	 */
+	public int size()
+	{
+		return _numElements;
+	}
+	
+	/**
+	 * The _maximum_ number of items that the list can hold, as defined on instantiation.
+	 * (Not to be confused with the Buffer's capacity)
+	 */
+	public int capacity()
+	{
+		return _b.capacity() / PROPERTIES_PER_ELEMENT;
+	}
+	
+	/**
+	 * Clear object in preparation for garbage collection
+	 */
+	public void clear()
+	{
+		_b.clear();
+	}
+
+	public Color4 getAsColor4(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return new Color4( _b.get(), _b.get(), _b.get(), _b.get() );
+	}
+	
+	public void putInColor4(int $index, Color4 $color4)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		$color4.r = (short)_b.get();
+		$color4.g = (short)_b.get();
+		$color4.b = (short)_b.get();
+		$color4.a = (short)_b.get();
+	}
+
+	public short getPropertyR(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return (short)_b.get();
+	}
+	public short getPropertyG(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		return (short)_b.get();
+	}
+	public float getPropertyB(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		return (short)_b.get();
+	}
+	public float getPropertyA(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 3);
+		return (short)_b.get();
+	}
+	
+	//
+	
+	public void add(Color4 $c)
+	{
+		set( _numElements, $c );
+		_numElements++;
+	}
+	
+	public void add(short $r, short $g, short $b, short $a)
+	{
+		set(_numElements, $r, $g, $b, $a);
+		_numElements++;
+	}
+	
+	public void set(int $index, Color4 $c)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put((byte)$c.r);
+		_b.put((byte)$c.g);
+		_b.put((byte)$c.b);
+		_b.put((byte)$c.a);
+		
+		// Rem, OpenGL takes in color in this order: r,g,b,a -- _not_ a,r,g,b
+	}
+
+	public void set(int $index, short $r, short $g, short $b, short $a)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put((byte)$r);
+		_b.put((byte)$g);
+		_b.put((byte)$b);
+		_b.put((byte)$a);
+	}
+	
+	public void setPropertyR(int $index, short $r)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put((byte)$r);
+	}
+	public void setPropertyG(int $index, short $g)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		_b.put((byte)$g);
+	}
+	public void setPropertyB(int $index, short $b)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		_b.put((byte)$b);
+	}
+	public void setPropertyA(int $index, short $a)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 3);
+		_b.put((byte)$a);
+	}
+	
+	//
+	
+	public ByteBuffer buffer()
+	{
+		return _b;
+	}
+	
+	public Color4BufferList clone()
+	{
+		_b.position(0);
+		Color4BufferList c = new Color4BufferList(_b, size());
+		return c;
+	}
+}

+ 190 - 0
app/src/main/java/min3d/core/FacesBufferedList.java

@@ -0,0 +1,190 @@
+package min3d.core;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+
+import min3d.vos.Face;
+
+public class FacesBufferedList
+{
+	public static final int PROPERTIES_PER_ELEMENT = 3;
+	public static final int BYTES_PER_PROPERTY = 2;
+
+	private ShortBuffer _b;
+	private int _numElements;
+
+	private int _renderSubsetStartIndex = 0;
+	private int _renderSubsetLength = 1;
+	private boolean _renderSubsetEnabled = false;
+	
+	public FacesBufferedList(ShortBuffer $b, int $size)
+	{
+		ByteBuffer bb = ByteBuffer.allocateDirect($b.limit() * BYTES_PER_PROPERTY); 
+		bb.order(ByteOrder.nativeOrder());
+		_b = bb.asShortBuffer();
+		_b.put($b);
+		_numElements = $size;
+	}
+	
+	public FacesBufferedList(int $maxElements)
+	{
+		ByteBuffer b = ByteBuffer.allocateDirect($maxElements * PROPERTIES_PER_ELEMENT * BYTES_PER_PROPERTY); 
+		b.order(ByteOrder.nativeOrder());
+		_b = b.asShortBuffer();
+	}
+	
+	/**
+	 * The number of items in the list. 
+	 */
+	public int size()
+	{
+		return _numElements;
+	}
+	
+	
+	/**
+	 * The _maximum_ number of items that the list can hold, as defined on instantiation.
+	 * (Not to be confused with the Buffer's capacity)
+	 */
+	public int capacity()
+	{
+		return _b.capacity() / PROPERTIES_PER_ELEMENT;
+	}
+	
+	/**
+	 * Clear object in preparation for garbage collection
+	 */
+	public void clear()
+	{
+		_b.clear();
+	}
+
+	public Face get(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return new Face( _b.get(), _b.get(), _b.get() );
+	}
+	
+	public void putInFace(int $index, Face $face)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		$face.a = (short)_b.get();
+		$face.b = (short)_b.get();
+		$face.c = (short)_b.get();
+	}
+	
+	public short getPropertyA(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return (short)_b.get();
+	}
+	public short getPropertyB(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		return (short)_b.get();
+	}
+	public float getPropertyC(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		return (short)_b.get();
+	}
+
+	/**
+	 * Enables rendering only a subset of faces (renderSubset must be set to true) 
+	 * This mechanism could be expanded to render multiple 'subsets' of the list of faces...
+	 */
+	public void renderSubsetStartIndex(int $num)
+	{
+		_renderSubsetStartIndex = $num;
+	}
+	public int renderSubsetStartIndex()
+	{
+		return _renderSubsetStartIndex;
+	}
+	public void renderSubsetLength(int $num)
+	{
+		_renderSubsetLength = $num;
+	}
+	public int renderSubsetLength()
+	{
+		return _renderSubsetLength;
+	}
+	
+	/**
+	 * If true, Renderer will only draw the faces as defined by 
+	 * renderSubsetStartIndex and renderSubsetLength  
+	 */
+	public boolean renderSubsetEnabled()
+	{
+		return _renderSubsetEnabled;
+	}
+	public void renderSubsetEnabled(boolean $b)
+	{
+		_renderSubsetEnabled = $b;
+	}
+	
+	//
+	
+	public void add(Face $f)
+	{
+		set( _numElements, $f );
+		_numElements++;
+	}
+	
+	public void add(int $a, int $b, int $c) {
+		add((short)$a, (short)$b, (short)$c);
+	}
+	
+	public void add(short $a, short $b, short $c)
+	{
+		set(_numElements, $a, $b, $c);
+		_numElements++;
+	}
+	
+	public void set(int $index, Face $face)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($face.a);
+		_b.put($face.b);
+		_b.put($face.c);
+	}
+
+	public void set(int $index, short $a, short $b, short $c)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($a);
+		_b.put($b);
+		_b.put($c);
+	}
+	
+	public void setPropertyA(int $index, short $a)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($a);
+	}
+	public void setPropertyB(int $index, short $b)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		_b.put($b);
+	}
+	public void setPropertyC(int $index, short $c)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		_b.put($c);
+	}
+	
+	//
+	
+	public ShortBuffer buffer()
+	{
+		return _b;
+	}
+	
+	public FacesBufferedList clone()
+	{
+		_b.position(0);
+		FacesBufferedList c = new FacesBufferedList(_b, size());
+		return c;
+	}
+}

+ 149 - 0
app/src/main/java/min3d/core/ManagedLightList.java

@@ -0,0 +1,149 @@
+package min3d.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.util.Log;
+
+import min3d.Min3d;
+import min3d.vos.Light;
+
+public class ManagedLightList 
+{
+	// List of Light objects
+	private ArrayList<Light> _lights;
+
+	// Map Light objects to GL_LIGHT indices
+	private HashMap<Light, Integer> _lightToGlIndex;
+
+	// 'Pool' of available GL_LIGHT id's
+	private ArrayList<Integer> _availGlIndices;
+
+	// Array of which GL_LIGHTS are enabled, where index corresponds to
+	// GL_LIGHT[index]
+	private boolean[] _glIndexEnabled;
+
+	// Array of dirty flags, where index corresponds to GL_LIGHT[index]
+	private boolean[] _glIndexEnabledDirty;
+
+	// "GL index" here means an int from 0 to 8 that corresponds to
+	// the int constants GL10.GL_LIGHT0 to GL10.GL_LIGHT7
+
+	public ManagedLightList() 
+	{
+		reset();
+	}
+
+	public void reset() 
+	{
+		Log.i(Min3d.TAG, "ManagedLightList.reset()");
+
+		_availGlIndices = new ArrayList<Integer>();
+		for (int i = 0; i < Renderer.NUM_GLLIGHTS; i++) {
+			_availGlIndices.add(i);
+		}
+
+		_lightToGlIndex = new HashMap<Light, Integer>();
+
+		_glIndexEnabled = new boolean[Renderer.NUM_GLLIGHTS];
+		_glIndexEnabledDirty = new boolean[Renderer.NUM_GLLIGHTS];
+		for (int i = 0; i < Renderer.NUM_GLLIGHTS; i++) {
+			_glIndexEnabled[i] = false;
+			_glIndexEnabledDirty[i] = true;
+		}
+
+		_lights = new ArrayList<Light>();
+	}
+
+	public boolean add(Light $light) 
+	{
+		if (_lights.contains($light)) {
+			return false;
+		}
+
+		if (_lights.size() > Renderer.NUM_GLLIGHTS)
+			throw new Error("Exceeded maximum number of Lights");
+
+		boolean result = _lights.add($light);
+
+		int glIndex = _availGlIndices.remove(0);
+
+		_lightToGlIndex.put($light, glIndex);
+
+		_glIndexEnabled[glIndex] = true;
+		_glIndexEnabledDirty[glIndex] = true;
+		
+		return result;
+	}
+
+	public void remove(Light $light) 
+	{
+		boolean result = _lights.remove($light);
+
+		if (!result) return;
+
+		int glIndex = _lightToGlIndex.get($light);
+		
+		_availGlIndices.add(glIndex);
+
+		_glIndexEnabled[glIndex] = false;
+		_glIndexEnabledDirty[glIndex] = true;
+	}
+
+	public void removeAll() 
+	{
+		reset();
+	}
+
+	public int size() 
+	{
+		return _lights.size();
+	}
+
+	public Light get(int $index) 
+	{
+		return _lights.get($index);
+	}
+
+	public Light[] toArray() {
+		return (Light[]) _lights.toArray(new Light[_lights.size()]);
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	int getGlIndexByLight(Light $light) /* package-private */
+	{
+		return _lightToGlIndex.get($light);
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	Light getLightByGlIndex(int $glIndex) /* package-private */
+	{
+		for (int i = 0; i < _lights.size(); i++) 
+		{
+			Light light = _lights.get(i);
+			if (_lightToGlIndex.get(light) == $glIndex)
+				return light;
+		}
+		return null;
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	boolean[] glIndexEnabledDirty() /* package-private */
+	{
+		return _glIndexEnabledDirty;
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	boolean[] glIndexEnabled() /* package-private */
+	{
+		return _glIndexEnabled;
+	}
+}

+ 157 - 0
app/src/main/java/min3d/core/Number3dBufferList.java

@@ -0,0 +1,157 @@
+package min3d.core;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import min3d.vos.Number3d;
+
+public class Number3dBufferList
+{
+	public static final int PROPERTIES_PER_ELEMENT = 3;
+	public static final int BYTES_PER_PROPERTY = 4;
+
+	private FloatBuffer _b;
+	private int _numElements = 0;
+	
+	public Number3dBufferList(FloatBuffer $b, int $size)
+	{
+		ByteBuffer bb = ByteBuffer.allocateDirect($b.limit() * BYTES_PER_PROPERTY); 
+		bb.order(ByteOrder.nativeOrder());
+		_b = bb.asFloatBuffer();
+		_b.put($b);
+		_numElements = $size;
+	}
+	
+	public Number3dBufferList(int $maxElements)
+	{
+		int numBytes = $maxElements * PROPERTIES_PER_ELEMENT * BYTES_PER_PROPERTY;
+		ByteBuffer bb = ByteBuffer.allocateDirect(numBytes); 
+		bb.order(ByteOrder.nativeOrder());
+		
+		_b  = bb.asFloatBuffer();
+	}
+	
+	/**
+	 * The number of items in the list. 
+	 */
+	public int size()
+	{
+		return _numElements;
+	}
+	
+	/**
+	 * The _maximum_ number of items that the list can hold, as defined on instantiation.
+	 * (Not to be confused with the Buffer's capacity)
+	 */
+	public int capacity()
+	{
+		return _b.capacity() / PROPERTIES_PER_ELEMENT;
+	}
+	
+	/**
+	 * Clear object in preparation for garbage collection
+	 */
+	public void clear()
+	{
+		_b.clear();
+	}
+	
+	//
+	
+	public Number3d getAsNumber3d(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return new Number3d( _b.get(), _b.get(), _b.get() );
+	}
+	
+	public void putInNumber3d(int $index, Number3d $number3d)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		$number3d.x = _b.get();
+		$number3d.y = _b.get();
+		$number3d.z = _b.get();
+	}
+	
+	public float getPropertyX(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return _b.get();
+	}
+	public float getPropertyY(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		return _b.get();
+	}
+	public float getPropertyZ(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		return _b.get();
+	}
+	
+	//
+	
+	public void add(Number3d $n)
+	{
+		set( _numElements, $n );
+		_numElements++;
+	}
+	
+	public void add(float $x, float $y, float $z)
+	{
+		set( _numElements, $x,$y,$z );
+		_numElements++;
+	}
+	
+	public void set(int $index, Number3d $n)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($n.x);
+		_b.put($n.y);
+		_b.put($n.z);
+	}
+
+	public void set(int $index, float $x, float $y, float $z)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($x);
+		_b.put($y);
+		_b.put($z);
+	}
+	
+	public void setPropertyX(int $index, float $x)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($x);
+	}
+	public void setPropertyY(int $index, float $y)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		_b.put($y);
+	}
+	public void setPropertyZ(int $index, float $z)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 2);
+		_b.put($z);
+	}
+	
+	//
+	
+	public FloatBuffer buffer()
+	{
+		return _b;
+	}
+	
+	public void overwrite(float[] $newVals)
+	{
+		_b.position(0);
+		_b.put($newVals);
+	}
+	
+	public Number3dBufferList clone()
+	{
+		_b.position(0);
+		Number3dBufferList c = new Number3dBufferList(_b, size());
+		return c;
+	}
+}

+ 491 - 0
app/src/main/java/min3d/core/Object3d.java

@@ -0,0 +1,491 @@
+package min3d.core;
+
+import java.util.ArrayList;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import android.util.Log;
+
+import min3d.interfaces.IObject3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.RenderType;
+import min3d.vos.ShadeModel;
+
+/**
+ * @author Lee
+ */
+public class Object3d
+{
+	private String _name;
+	
+	private RenderType _renderType = RenderType.TRIANGLES;
+	
+	private boolean _isVisible = true;
+	private boolean _vertexColorsEnabled = true;
+	private boolean _doubleSidedEnabled = false;
+	private boolean _texturesEnabled = true;
+	private boolean _normalsEnabled = true;
+	private boolean _ignoreFaces = false;
+	private boolean _colorMaterialEnabled = false;
+	private boolean _lightingEnabled = true;
+
+	private Number3d _position = new Number3d(0,0,0);
+	private Number3d _rotation = new Number3d(0,0,0);
+	private Number3d _scale = new Number3d(1,1,1);
+
+	private Color4 _defaultColor = new Color4();
+	
+	private ShadeModel _shadeModel = ShadeModel.SMOOTH;
+	private float _pointSize = 3f;
+	private boolean _pointSmoothing = true;
+	private float _lineWidth = 1f;
+	private boolean _lineSmoothing = false;
+
+	
+	protected ArrayList<Object3d> _children;
+	
+	protected Vertices _vertices; 
+	protected TextureList _textures;
+	protected FacesBufferedList _faces;
+
+	protected boolean _animationEnabled = false;
+	
+	private Scene _scene;
+	private IObject3dContainer _parent;
+
+	/**
+	 * Maximum number of vertices and faces must be specified at instantiation.
+	 */
+	public Object3d(int $maxVertices, int $maxFaces)
+	{
+		_vertices = new Vertices($maxVertices, true,true,true);
+		_faces = new FacesBufferedList($maxFaces);
+		_textures = new TextureList();
+	}
+	
+	/**
+	 * Adds three arguments 
+	 */
+	public Object3d(int $maxVertices, int $maxFaces, Boolean $useUvs, Boolean $useNormals, Boolean $useVertexColors)
+	{
+		_vertices = new Vertices($maxVertices, $useUvs,$useNormals,$useVertexColors);
+		_faces = new FacesBufferedList($maxFaces);
+		_textures = new TextureList();
+	}
+	
+	/**
+	 * This constructor is convenient for cloning purposes 
+	 */
+	public Object3d(Vertices $vertices, FacesBufferedList $faces, TextureList $textures)
+	{
+		_vertices = $vertices;
+		_faces = $faces;
+		_textures = $textures;
+	}
+	
+	/**
+	 * Holds references to vertex position list, vertex u/v mappings list, vertex normals list, and vertex colors list
+	 */
+	public Vertices vertices()
+	{
+		return _vertices;
+	}
+
+	/**
+	 * List of object's faces (ie, index buffer) 
+	 */
+	public FacesBufferedList faces()
+	{
+		return _faces;
+	}
+	
+	public TextureList textures()
+	{
+		return _textures;
+	}
+	
+	/**
+	 * Determines if object will be rendered.
+	 * Default is true. 
+	 */
+	public boolean isVisible()
+	{
+		return _isVisible;
+	}
+	public void isVisible(Boolean $b)
+	{
+		_isVisible = $b;
+	}
+	
+	/**
+	 * Determines if backfaces will be rendered (ie, doublesided = true).
+	 * Default is false.
+	 */
+	public boolean doubleSidedEnabled()
+	{
+		return _doubleSidedEnabled;
+	}
+	public void doubleSidedEnabled(boolean $b)
+	{
+		_doubleSidedEnabled = $b;
+	}
+	
+	/**
+	 * Determines if object uses GL_COLOR_MATERIAL or not.
+	 * Default is false.
+	 */
+	public boolean colorMaterialEnabled()
+	{
+		return _colorMaterialEnabled;
+	}
+	
+	public boolean lightingEnabled() {
+		return _lightingEnabled;
+	}
+
+	public void lightingEnabled(boolean $b) {
+		this._lightingEnabled = $b;
+	}
+
+	public void colorMaterialEnabled(boolean $b)
+	{
+		_colorMaterialEnabled = $b;
+	}
+
+	/**
+	 * Determines whether animation is enabled or not. If it is enabled
+	 * then this should be an AnimationObject3d instance.
+	 * This is part of the Object3d class so there's no need to cast
+	 * anything during the render loop when it's not necessary.
+	 */
+	public boolean animationEnabled()
+	{
+		return _animationEnabled;
+	}
+	public void animationEnabled(boolean $b)
+	{
+		_animationEnabled = $b;
+	}
+	/**
+	 * Determines if per-vertex colors will be using for rendering object.
+	 * If false, defaultColor property will dictate object color.
+	 * If object has no per-vertex color information, setting is ignored.
+	 * Default is true. 
+	 */
+	public boolean vertexColorsEnabled()
+	{
+		return _vertexColorsEnabled;
+	}
+	public void vertexColorsEnabled(Boolean $b)
+	{
+		_vertexColorsEnabled = $b;
+	}
+
+	/**
+	 * Determines if textures (if any) will used for rendering object.
+	 * Default is true.  
+	 */
+	public boolean texturesEnabled()
+	{
+		return _texturesEnabled;
+	}
+	public void texturesEnabled(Boolean $b)
+	{
+		_texturesEnabled = $b;
+	}
+	
+	/**
+	 * Determines if object will be rendered using vertex light normals.
+	 * If false, no lighting is used on object for rendering.
+	 * Default is true.
+	 */
+	public boolean normalsEnabled()
+	{
+		return _normalsEnabled;
+	}
+	public void normalsEnabled(boolean $b)
+	{
+		_normalsEnabled = $b;
+	}
+
+	/**
+	 * When true, Renderer draws using vertex points list, rather than faces list.
+	 * (ie, using glDrawArrays instead of glDrawElements) 
+	 * Default is false.
+	 */
+	public boolean ignoreFaces()
+	{
+		return _ignoreFaces;
+	}
+	public void ignoreFaces(boolean $b)
+	{
+		_ignoreFaces = $b;
+	}	
+	
+	/**
+	 * Options are: TRIANGLES, LINES, and POINTS
+	 * Default is TRIANGLES.
+	 */
+	public RenderType renderType()
+	{
+		return _renderType;
+	}
+	public void renderType(RenderType $type)
+	{
+		_renderType = $type;
+	}
+	
+	/**
+	 * Possible values are ShadeModel.SMOOTH and ShadeModel.FLAT.
+	 * Default is ShadeModel.SMOOTH.
+	 * @return
+	 */
+	public ShadeModel shadeModel()
+	{
+		return _shadeModel;
+	}
+	public void shadeModel(ShadeModel $shadeModel)
+	{
+		_shadeModel = $shadeModel;
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public Number3dBufferList points()
+	{
+		return _vertices.points();
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public UvBufferList uvs()
+	{
+		return _vertices.uvs();
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public Number3dBufferList normals()
+	{
+		return _vertices.normals();
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public Color4BufferList colors()
+	{
+		return _vertices.colors();
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public boolean hasUvs()
+	{
+		return _vertices.hasUvs();
+	}
+
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public boolean hasNormals()
+	{
+		return _vertices.hasNormals();
+	}
+	
+	/**
+	 * Convenience 'pass-thru' method  
+	 */
+	public boolean hasVertexColors()
+	{
+		return _vertices.hasColors();
+	}
+
+
+	/**
+	 * Clear object for garbage collection.
+	 */
+	public void clear()
+	{
+		if (this.vertices().points() != null) 	this.vertices().points().clear();
+		if (this.vertices().uvs() != null) 		this.vertices().uvs().clear();
+		if (this.vertices().normals() != null) 	this.vertices().normals().clear();
+		if (this.vertices().colors() != null) 	this.vertices().colors().clear();
+		if (_textures != null) 					_textures.clear();
+		
+		if (this.parent() != null) 				this.parent().removeChild(this);
+	}
+
+	//
+
+	/**
+	 * Color used to render object, but only when colorsEnabled is false.
+	 */
+	public Color4 defaultColor()
+	{
+		return _defaultColor;
+	}
+	
+	public void defaultColor(Color4 color) {
+		_defaultColor = color;
+	}
+
+	/**
+	 * X/Y/Z position of object. 
+	 */
+	public Number3d position()
+	{
+		return _position;
+	}
+	
+	/**
+	 * X/Y/Z euler rotation of object, using Euler angles.
+	 * Units should be in degrees, to match OpenGL usage. 
+	 */
+	public Number3d rotation()
+	{
+		return _rotation;
+	}
+
+	/**
+	 * X/Y/Z scale of object.
+	 */
+	public Number3d scale()
+	{
+		return _scale;
+	}
+	
+	/**
+	 * Point size (applicable when renderType is POINT)
+	 * Default is 3. 
+	 */
+	public float pointSize()
+	{
+		return _pointSize; 
+	}
+	public void pointSize(float $n)
+	{
+		_pointSize = $n;
+	}
+
+	/**
+	 * Point smoothing (anti-aliasing), applicable when renderType is POINT.
+	 * When true, points look like circles rather than squares.
+	 * Default is true.
+	 */
+	public boolean pointSmoothing()
+	{
+		return _pointSmoothing;
+	}
+	public void pointSmoothing(boolean $b)
+	{
+		_pointSmoothing = $b;
+	}
+
+	/**
+	 * Line width (applicable when renderType is LINE)
+	 * Default is 1. 
+	 * 
+	 * Remember that maximum line width is OpenGL-implementation specific, and varies depending 
+	 * on whether lineSmoothing is enabled or not. Eg, on Nexus One,  lineWidth can range from
+	 * 1 to 8 without smoothing, and can only be 1f with smoothing. 
+	 */
+	public float lineWidth()
+	{
+		return _lineWidth;
+	}
+	public void lineWidth(float $n)
+	{
+		_lineWidth = $n;
+	}
+	
+	/**
+	 * Line smoothing (anti-aliasing), applicable when renderType is LINE
+	 * Default is false.
+	 */
+	public boolean lineSmoothing()
+	{
+		return _lineSmoothing;
+	}
+	public void lineSmoothing(boolean $b)
+	{
+		_lineSmoothing = $b;
+	}
+	
+	/**
+	 * Convenience property 
+	 */
+	public String name()
+	{
+		return _name;
+	}
+	public void name(String $s)
+	{
+		_name = $s;
+	}
+	
+	public IObject3dContainer parent()
+	{
+		return _parent;
+	}
+	
+	//
+	
+	void parent(IObject3dContainer $container) /*package-private*/
+	{
+		_parent = $container;
+	}
+	
+	/**
+	 * Called by Scene
+	 */
+	void scene(Scene $scene) /*package-private*/
+	{
+		_scene = $scene;
+	}
+	/**
+	 * Called by DisplayObjectContainer
+	 */
+	Scene scene() /*package-private*/
+	{
+		return _scene;
+	}
+	
+	/**
+	 * Can be overridden to create custom draw routines on a per-object basis, 
+	 * rather than using Renderer's built-in draw routine. 
+	 * 
+	 * If overridden, return true instead of false.
+	 */
+	public Boolean customRenderer(GL10 gl)
+	{
+		return false;
+	}
+	
+	public Object3d clone()
+	{
+		Vertices v = _vertices.clone();
+		FacesBufferedList f = _faces.clone();
+			
+		Object3d clone = new Object3d(v, f, _textures);
+		
+		clone.position().x = position().x;
+		clone.position().y = position().y;
+		clone.position().z = position().z;
+		
+		clone.rotation().x = rotation().x;
+		clone.rotation().y = rotation().y;
+		clone.rotation().z = rotation().z;
+		
+		clone.scale().x = scale().x;
+		clone.scale().y = scale().y;
+		clone.scale().z = scale().z;
+		
+		return clone;
+	}
+}

+ 137 - 0
app/src/main/java/min3d/core/Object3dContainer.java

@@ -0,0 +1,137 @@
+package min3d.core;
+
+import java.util.ArrayList;
+
+import min3d.interfaces.IObject3dContainer;
+
+public class Object3dContainer extends Object3d implements IObject3dContainer
+{
+	protected ArrayList<Object3d> _children = new ArrayList<Object3d>();
+
+	public Object3dContainer()
+	{
+		super(0, 0, false, false, false);
+	}
+	/**
+	 * Adds container functionality to Object3d.
+	 * 
+	 * Subclass Object3dContainer instead of Object3d if you
+	 * believe you may want to add children to that object. 
+	 */
+	public Object3dContainer(int $maxVerts, int $maxFaces)
+	{
+		super($maxVerts, $maxFaces, true,true,true);
+	}
+
+	public Object3dContainer(int $maxVerts, int $maxFaces,  Boolean $useUvs, Boolean $useNormals, Boolean $useVertexColors)
+	{
+		super($maxVerts, $maxFaces, $useUvs,$useNormals,$useVertexColors);
+	}
+	
+	/**
+	 * This constructor is convenient for cloning purposes 
+	 */
+	public Object3dContainer(Vertices $vertices, FacesBufferedList $faces, TextureList $textures)
+	{
+		super($vertices, $faces, $textures);
+	}
+	
+	public void addChild(Object3d $o)
+	{
+		_children.add($o);
+		
+		$o.parent(this);
+		$o.scene(this.scene());
+	}
+	
+	public void addChildAt(Object3d $o, int $index) 
+	{
+		_children.add($index, $o);
+		
+		$o.parent(this);
+		$o.scene(this.scene());
+	}
+
+	public boolean removeChild(Object3d $o)
+	{
+		boolean b = _children.remove($o);
+		
+		if (b) {
+			$o.parent(null);
+			$o.scene(null);
+		}
+		return b;
+	}
+	
+	public Object3d removeChildAt(int $index) 
+	{
+		Object3d o = _children.remove($index);
+		if (o != null) {
+			o.parent(null);
+			o.scene(null);
+		}
+		return o;
+	}
+	
+	public Object3d getChildAt(int $index) 
+	{
+		return _children.get($index);
+	}
+
+	/**
+	 * TODO: Use better lookup 
+	 */
+	public Object3d getChildByName(String $name)
+	{
+		for (int i = 0; i < _children.size(); i++)
+		{
+			if (_children.get(i).name().equals($name)) return _children.get(i); 
+		}
+		return null;
+	}
+
+	public int getChildIndexOf(Object3d $o) 
+	{
+		return _children.indexOf($o);		
+	}
+
+
+	public int numChildren() 
+	{
+		return _children.size();
+	}
+	
+	/*package-private*/ 
+	ArrayList<Object3d> children()
+	{
+		return _children;
+	}
+	
+	public Object3dContainer clone()
+	{
+		Vertices v = _vertices.clone();
+		FacesBufferedList f = _faces.clone();
+
+		Object3dContainer clone = new Object3dContainer(v, f, _textures);
+		
+		clone.position().x = position().x;
+		clone.position().y = position().y;
+		clone.position().z = position().z;
+		
+		clone.rotation().x = rotation().x;
+		clone.rotation().y = rotation().y;
+		clone.rotation().z = rotation().z;
+		
+		clone.scale().x = scale().x;
+		clone.scale().y = scale().y;
+		clone.scale().z = scale().z;
+		
+		for(int i = 0; i< this.numChildren();i++)
+		{
+			 clone.addChild(this.getChildAt(i));
+		}
+		 
+		return clone;
+	}
+
+}

+ 153 - 0
app/src/main/java/min3d/core/RenderCaps.java

@@ -0,0 +1,153 @@
+package min3d.core;
+
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+import min3d.Min3d;
+
+import android.util.Log;
+
+/**
+ * Simple static class holding values representing various capabilities of 
+ * hardware's concrete OpenGL capabilities that are relevant to min3d's 
+ * supported features. 
+ */
+public class RenderCaps 
+{
+	private static float _openGlVersion;
+	private static boolean _isGl10Only;
+	private static int _maxTextureUnits;
+	private static int _maxTextureSize;
+	private static int _aliasedPointSizeMin;
+	private static int _aliasedPointSizeMax;
+	private static int _smoothPointSizeMin;
+	private static int _smoothPointSizeMax;
+	private static int _aliasedLineSizeMin;
+	private static int _aliasedLineSizeMax;
+	private static int _smoothLineSizeMin;
+	private static int _smoothLineSizeMax;
+	private static int _maxLights;
+	
+	
+	public static float openGlVersion()
+	{
+		return _openGlVersion;
+	}
+	
+	public static boolean isGl10Only()
+	{
+		return _isGl10Only;
+	}
+
+	public static int maxTextureUnits()
+	{
+		return _maxTextureUnits;
+	}
+	
+	public static int aliasedPointSizeMin()
+	{
+		return _aliasedPointSizeMin;
+	}
+	
+	public static int aliasedPointSizeMax()
+	{
+		return _aliasedPointSizeMax;
+	}
+	
+	public static int smoothPointSizeMin()
+	{
+		return _smoothPointSizeMin;
+	}
+	
+	public static int smoothPointSizeMax()
+	{
+		return _smoothPointSizeMax;
+	}
+	
+	public static int aliasedLineSizeMin()
+	{
+		return _aliasedLineSizeMin;
+	}
+	
+	public static int aliasedLineSizeMax()
+	{
+		return _aliasedLineSizeMax;
+	}
+	
+	public static int smoothLineSizeMin()
+	{
+		return _smoothLineSizeMin;
+	}
+	
+	public static int smoothLineSizeMax()
+	{
+		return _smoothLineSizeMax;
+	}
+	
+	public static int maxLights()
+	{
+		return _maxLights;
+	}
+	
+	/**
+	 * Called by Renderer.onSurfaceCreate() 
+	 */
+	static void setRenderCaps(GL10 $gl) /* package-private*/
+	{
+	    IntBuffer i;
+
+	    // OpenGL ES version
+		if ($gl instanceof GL11) {
+			_openGlVersion = 1.1f;
+		}
+		else {
+			_openGlVersion = 1.0f;
+		}
+		
+	    // Max texture units
+		i = IntBuffer.allocate(1);
+		$gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_UNITS, i);
+		_maxTextureUnits = i.get(0);
+		
+	    // Max texture size
+		i = IntBuffer.allocate(1);
+		$gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, i);
+		_maxTextureSize = i.get(0);
+		
+		// Aliased point size range
+		i = IntBuffer.allocate(2);
+		$gl.glGetIntegerv(GL10.GL_ALIASED_POINT_SIZE_RANGE, i);
+		_aliasedPointSizeMin = i.get(0);
+		_aliasedPointSizeMax = i.get(1);
+
+		// Smooth point size range
+		i = IntBuffer.allocate(2);
+		$gl.glGetIntegerv(GL10.GL_SMOOTH_POINT_SIZE_RANGE, i);
+		_smoothPointSizeMin = i.get(0);
+		_smoothPointSizeMax = i.get(1);
+
+		// Aliased line width range
+		i = IntBuffer.allocate(2);
+		$gl.glGetIntegerv(GL10.GL_ALIASED_LINE_WIDTH_RANGE, i);
+		_aliasedLineSizeMin = i.get(0);
+		_aliasedLineSizeMax = i.get(1);
+
+		// Smooth line width range
+		i = IntBuffer.allocate(2);
+		$gl.glGetIntegerv(GL10.GL_SMOOTH_LINE_WIDTH_RANGE, i);
+		_smoothLineSizeMin = i.get(0);
+		_smoothLineSizeMax = i.get(1);
+		
+	    // Max lights
+		i = IntBuffer.allocate(1);
+		$gl.glGetIntegerv(GL10.GL_MAX_LIGHTS, i);
+		_maxLights = i.get(0);
+
+		Log.v(Min3d.TAG, "RenderCaps - openGLVersion: " + _openGlVersion);
+		Log.v(Min3d.TAG, "RenderCaps - maxTextureUnits: " + _maxTextureUnits);
+		Log.v(Min3d.TAG, "RenderCaps - maxTextureSize: " + _maxTextureSize);
+		Log.v(Min3d.TAG, "RenderCaps - maxLights: " + _maxLights);
+	}
+}

+ 682 - 0
app/src/main/java/min3d/core/Renderer.java

@@ -0,0 +1,682 @@
+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.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 clearScene()
+	{
+		_scene.clear();
+	}
+
+	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())
+		{
+			_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.direction.isDirty())
+				{
+					light.direction.commitToFloatBuffer();
+					_gl.glLightfv(glLightId, GL10.GL_SPOT_DIRECTION, light.direction.floatBuffer());
+					light.direction.clearDirtyFlag();
+				}
+				if (light._spotCutoffAngle.isDirty())
+				{
+					_gl.glLightf(glLightId, GL10.GL_SPOT_CUTOFF, light._spotCutoffAngle.get());
+				}
+				if (light._spotExponent.isDirty())
+				{
+					_gl.glLightf(glLightId, GL10.GL_SPOT_EXPONENT, light._spotExponent.get());
+				}
+
+				if (light._isVisible.isDirty()) 
+				{
+					if (light.isVisible()) {
+						_gl.glEnable(glLightId);
+					} else {
+						_gl.glDisable(glLightId);
+					}
+					light._isVisible.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();
+		
+		_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
+		//
+	}
+}

+ 204 - 0
app/src/main/java/min3d/core/RendererActivity.java

@@ -0,0 +1,204 @@
+package min3d.core;
+
+import min3d.Shared;
+import min3d.interfaces.ISceneController;
+import android.app.Activity;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.os.Handler;
+
+/**
+ * Extend this class when creating your min3d-based Activity. 
+ * Then, override initScene() and updateScene() for your main
+ * 3D logic.
+ * 
+ * Override onCreateSetContentView() to change layout, if desired.
+ * 
+ * To update 3d scene-related variables from within the the main UI thread,  
+ * override onUpdateScene() and onUpdateScene() as needed.
+ */
+public class RendererActivity extends Activity implements ISceneController
+{
+	public static Scene scene;
+	public static Renderer mRender;
+	protected GLSurfaceView _glSurfaceView;
+	
+	protected Handler _initSceneHander;
+	protected Handler _updateSceneHander;
+	
+    private boolean _renderContinuously;
+    
+
+	final Runnable _initSceneRunnable = new Runnable() 
+	{
+        public void run() {
+            onInitScene();
+        }
+    };
+    
+	final Runnable _updateSceneRunnable = new Runnable() 
+    {
+        public void run() {
+            onUpdateScene();
+        }
+    };
+    
+
+	public static void initStatic(Activity a)
+	{
+		Shared.context(a);
+		scene = new Scene(null);
+		mRender= new Renderer(scene);
+		Shared.renderer(mRender);
+	}
+
+    @Override
+	protected void onCreate(Bundle savedInstanceState) 
+	{
+		super.onCreate(savedInstanceState);
+
+		_initSceneHander = new Handler();
+		_updateSceneHander = new Handler();
+		
+		//
+		// These 4 lines are important.
+		//
+		_glSurfaceView = new GLSurfaceView(this);
+
+		_glSurfaceView.setZOrderOnTop(true);
+		_glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
+		_glSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
+
+
+		//ici
+		Shared.context(this);
+		//scene.sceneController(this);
+		scene = new Scene(this);
+		mRender= new Renderer(scene);
+		Shared.renderer(mRender);
+
+        glSurfaceViewConfig();
+		_glSurfaceView.setRenderer(mRender);
+		//_glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+		_glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+
+        onCreateSetContentView();
+	}
+    
+    /**
+     * Any GlSurfaceView settings that needs to be executed before 
+     * GLSurfaceView.setRenderer() can be done by overriding this method. 
+     * A couple examples are included in comments below.
+     */
+    protected void glSurfaceViewConfig()
+    {
+	    // Example which makes glSurfaceView transparent (along with setting scene.backgroundColor to 0x0)
+		_glSurfaceView.setZOrderOnTop(true);
+	     _glSurfaceView.setEGLConfigChooser(8,8,8,8, 16, 0);
+	     _glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); _glSurfaceView.getHolder().setFormat( PixelFormat.RGB_565 );
+
+
+		// Example of enabling logging of GL operations 
+		// _glSurfaceView.setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR | GLSurfaceView.DEBUG_LOG_GL_CALLS);
+    }
+	
+	protected GLSurfaceView glSurfaceView()
+	{
+		return _glSurfaceView;
+	}
+	
+	/**
+	 * Separated out for easier overriding...
+	 */
+	protected void onCreateSetContentView()
+	{
+		setContentView(_glSurfaceView);
+	}
+	
+	@Override
+	protected void onResume() 
+	{
+		super.onResume();
+		mRender.clearScene();
+		_glSurfaceView.onResume();
+	}
+	
+	@Override
+	protected void onPause() 
+	{
+		super.onPause();
+		mRender.clearScene();
+		_glSurfaceView.onPause();
+	}
+
+	/**
+	 * Instantiation of Object3D's, setting their properties, and adding Object3D's 
+	 * to the scene should be done here. Or any point thereafter.
+	 * 
+	 * Note that this method is always called after GLCanvas is created, which occurs
+	 * not only on Activity.onCreate(), but on Activity.onResume() as well.
+	 * It is the user's responsibility to build the logic to restore state on-resume.
+	 */
+	public void initScene()
+	{
+	}
+
+	/**
+	 * All manipulation of scene and Object3D instance properties should go here.
+	 * Gets called on every frame, right before rendering.   
+	 */
+	public void updateScene()
+	{
+	}
+	
+    /**
+     * Called _after_ scene init (ie, after initScene).
+     * Unlike initScene(), gets called from the UI thread.
+     */
+    public void onInitScene()
+    {
+    }
+    
+    /**
+     * Called _after_ updateScene()
+     * Unlike initScene(), gets called from the UI thread.
+     */
+    public void onUpdateScene()
+    {
+    }
+    
+    /**
+     * Setting this to false stops the render loop, and initScene() and onInitScene() will no longer fire.
+     * Setting this to true resumes it. 
+     */
+    public void renderContinuously(boolean $b)
+    {
+    	_renderContinuously = $b;
+    	if (_renderContinuously)
+    		_glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+    	
+    	else
+    		_glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+    }
+    
+	public Handler getInitSceneHandler()
+	{
+		return _initSceneHander;
+	}
+	
+	public Handler getUpdateSceneHandler()
+	{
+		return _updateSceneHander;
+	}
+
+    public Runnable getInitSceneRunnable()
+    {
+    	return _initSceneRunnable;
+    }
+	
+    public Runnable getUpdateSceneRunnable()
+    {
+    	return _updateSceneRunnable;
+    }
+}

+ 297 - 0
app/src/main/java/min3d/core/Scene.java

@@ -0,0 +1,297 @@
+package min3d.core;
+
+import java.util.ArrayList;
+
+import min3d.Min3d;
+import min3d.interfaces.IDirtyParent;
+import min3d.interfaces.IObject3dContainer;
+import min3d.interfaces.ISceneController;
+import min3d.vos.CameraVo;
+import min3d.vos.Color4;
+import min3d.vos.Color4Managed;
+import min3d.vos.FogType;
+import android.util.Log;
+
+
+public class Scene implements IObject3dContainer, IDirtyParent
+{
+	private ArrayList<Object3d> _children = new ArrayList<Object3d>();
+
+	private ManagedLightList _lights;
+	private CameraVo _camera;
+	
+	private Color4Managed _backgroundColor;
+	private boolean _lightingEnabled;
+	
+	private Color4 _fogColor;
+	private float _fogFar;
+	private float _fogNear;
+	private FogType _fogType;
+	private boolean _fogEnabled;
+
+	private ISceneController _sceneController;
+	
+
+	public Scene(ISceneController $sceneController) 
+	{
+		_sceneController = $sceneController;
+		_lights = new ManagedLightList();
+		_fogColor = new Color4(255, 255, 255, 255);
+		_fogNear = 0;
+		_fogFar = 10;
+		_fogType = FogType.LINEAR;
+		_fogEnabled = false;
+	}
+
+	/**
+	 * Allows you to use any Class implementing ISceneController
+	 * to drive the Scene...
+	 * @return
+	 */
+	public ISceneController sceneController()
+	{
+		return _sceneController;
+	}
+	public void sceneController(ISceneController $sceneController)
+	{
+		_sceneController = $sceneController;
+	}
+	
+	//
+	
+	/**
+	 * Resets Scene to default settings.
+	 * Removes and clears any attached Object3ds.
+	 * Resets light list.
+	 */
+	public void reset()
+	{
+		clearChildren(this);
+
+		_children = new ArrayList<Object3d>();
+
+		_camera = new CameraVo();
+		
+		_backgroundColor = new Color4Managed(0,0,0,255, this);
+		
+		_lights = new ManagedLightList();
+		
+		lightingEnabled(true);
+	}
+	
+	/**
+	 * Adds Object3d to Scene. Object3d's must be added to Scene in order to be rendered
+	 * Returns always true. 
+	 */
+	public void addChild(Object3d $o)
+	{
+		if (_children.contains($o)) return;
+		
+		_children.add($o);
+		
+		$o.parent(this);
+		$o.scene(this);
+	}
+
+	public void clear()
+	{
+		_children.clear();
+	}
+	
+	public void addChildAt(Object3d $o, int $index)
+	{
+		if (_children.contains($o)) return;
+
+		_children.add($index, $o);
+	}
+	
+	/**
+	 * Removes Object3d from Scene.
+	 * Returns false if unsuccessful
+	 */
+	public boolean removeChild(Object3d $o)
+	{
+		$o.parent(null);
+		$o.scene(null);
+		return _children.remove($o);
+	}
+	
+	public Object3d removeChildAt(int $index)
+	{
+		Object3d o = _children.remove($index);
+		
+		if (o != null) {
+			o.parent(null);
+			o.scene(null);
+		}
+		return o;
+	}
+	
+	public Object3d getChildAt(int $index)
+	{
+		return _children.get($index);
+	}
+	
+	/**
+	 * TODO: Use better lookup 
+	 */
+	public Object3d getChildByName(String $name)
+	{
+		for (int i = 0; i < _children.size(); i++)
+		{
+			if (_children.get(0).name() == $name) return _children.get(0); 
+		}
+		return null;
+	}
+	
+	public int getChildIndexOf(Object3d $o)
+	{
+		return _children.indexOf($o);
+	}
+	
+	public int numChildren()
+	{
+		return _children.size();
+	}
+
+	/**
+	 * Scene's camera
+	 */
+	public CameraVo camera()
+	{
+		return _camera;
+	}
+	public void camera(CameraVo $camera)
+	{
+		_camera = $camera;
+	}
+	
+	/**
+	 * Scene instance's background color
+	 */
+	public Color4Managed backgroundColor()
+	{
+		return _backgroundColor;
+	}
+
+	/**
+	 * Lights used by the Scene 
+	 */
+	public ManagedLightList lights()
+	{
+		return _lights;
+	}
+
+	/**
+	 * Determines if lighting is enabled for Scene. 
+	 */
+	public boolean lightingEnabled()
+	{
+		return _lightingEnabled;
+	}
+	
+	public void lightingEnabled(boolean $b)
+	{
+		_lightingEnabled = $b;
+	}
+	
+	//
+
+	/*
+	public boolean backgroundTransparent() {
+		return _backgroundTransparent;
+	}
+
+	public void backgroundTransparent(boolean backgroundTransparent) {
+		this._backgroundTransparent = backgroundTransparent;
+	}
+	*/
+
+	public Color4 fogColor() {
+		return _fogColor;
+	}
+
+	public void fogColor(Color4 _fogColor) {
+		this._fogColor = _fogColor;
+	}
+
+	public float fogFar() {
+		return _fogFar;
+	}
+
+	public void fogFar(float _fogFar) {
+		this._fogFar = _fogFar;
+	}
+
+	public float fogNear() {
+		return _fogNear;
+	}
+
+	public void fogNear(float _fogNear) {
+		this._fogNear = _fogNear;
+	}
+
+	public FogType fogType() {
+		return _fogType;
+	}
+
+	public void fogType(FogType _fogType) {
+		this._fogType = _fogType;
+	}
+
+	public boolean fogEnabled() {
+		return _fogEnabled;
+	}
+
+	public void fogEnabled(boolean _fogEnabled) {
+		this._fogEnabled = _fogEnabled;
+	}
+
+	/**
+	 * Used by Renderer 
+	 */
+	void init() /*package-private*/ 
+	{
+		Log.i(Min3d.TAG, "Scene.init()");
+		
+		this.reset();
+		
+		_sceneController.initScene();
+		_sceneController.getInitSceneHandler().post(_sceneController.getInitSceneRunnable());
+	}
+	
+	void update()
+	{
+		_sceneController.updateScene();
+		_sceneController.getUpdateSceneHandler().post(_sceneController.getUpdateSceneRunnable());
+	}
+	
+	/**
+	 * Used by Renderer 
+	 */
+	ArrayList<Object3d> children() /*package-private*/ 
+	{
+		return _children;
+	}
+	
+	private void clearChildren(IObject3dContainer $c)
+	{
+		for (int i = $c.numChildren() - 1; i >= 0; i--)
+		{
+			Object3d o = $c.getChildAt(i);
+			o.clear();
+			
+			if (o instanceof Object3dContainer)
+			{
+				clearChildren((Object3dContainer)o);
+			}
+		}
+	}	
+	
+	public void onDirty()
+	{
+		//
+	}
+
+
+}

+ 155 - 0
app/src/main/java/min3d/core/TextureList.java

@@ -0,0 +1,155 @@
+package min3d.core;
+
+import java.util.ArrayList;
+
+import min3d.Shared;
+import min3d.vos.TextureVo;
+
+/**
+ * Manages a list of TextureVo's used by Object3d's.
+ * This allows an Object3d to use multiple textures. 
+ * 
+ * If more textures are added than what's supported by the hardware  
+ * running the application, the extra items are ignored by Renderer
+ * 
+ * Uses a subset of ArrayList's methods. 
+ */
+public class TextureList  
+{
+	private ArrayList<TextureVo> _t;
+	
+	
+	public TextureList()
+	{
+		_t = new ArrayList<TextureVo>();
+	}
+	
+	/**
+	 * Adds item to the list 
+	 */
+	public boolean add(TextureVo $texture)
+	{
+		if (! Shared.textureManager().contains($texture.textureId)) return false;
+		return _t.add($texture);
+	}
+	
+	/**
+	 * Adds item at the given position to the list 
+	 */
+	public void add(int $index, TextureVo $texture)
+	{
+		_t.add($index, $texture);
+	}
+	
+	/**
+	 * Adds a new TextureVo with the given textureId to the list, and returns that textureVo  
+	 */
+	public TextureVo addById(String $textureId)
+	{
+		if (! Shared.textureManager().contains($textureId)) {
+			throw new Error("Could not create TextureVo using textureId \"" + $textureId + "\". TextureManager does not contain that id.");
+		}
+		
+		TextureVo t = new TextureVo($textureId);
+		_t.add(t);
+		return t;
+	}
+	
+	/**
+	 * Adds texture as the sole item in the list, replacing any existing items  
+	 */
+	public boolean addReplace(TextureVo $texture)
+	{
+		_t.clear();
+		return _t.add($texture);
+	}
+	
+	/**
+	 * Removes item from the list 
+	 */
+	public boolean remove(TextureVo $texture)
+	{
+		return _t.remove($texture);
+	}
+	
+	/**
+	 * Removes item with the given textureId from the list 
+	 */
+	public boolean removeById(String $textureId)
+	{
+		TextureVo t = this.getById($textureId);
+		if (t == null) {
+			throw new Error("No match in TextureList for id \"" + $textureId + "\"");
+		}
+		return _t.remove(t);
+	}
+	
+	public void removeAll()
+	{
+		for (int i = 0; i < _t.size(); i++)
+			_t.remove(0);
+	}
+	
+	/**
+	 * Get item from the list which is at the given index position 
+	 */
+	public TextureVo get(int $index)
+	{
+		return _t.get($index);
+	}
+	
+	/**
+	 * Gets item from the list which has the given textureId
+	 */
+	public TextureVo getById(String $textureId)
+	{
+		for (int i = 0; i < _t.size(); i++) {
+			String s = _t.get(i).textureId;
+			if ($textureId == s) {
+				TextureVo t = _t.get(i);
+				return t;
+			}
+		}
+		return null;
+	}
+	
+	public int size()
+	{
+		return _t.size();
+	}
+	
+	public void clear()
+	{
+		_t.clear();
+	}
+	
+	/**
+	 * Return a TextureVo array of TextureList's items 
+	 */
+	public TextureVo[] toArray()
+	{
+		Object[] a = _t.toArray();
+		TextureVo[] ret = new TextureVo[a.length];
+		for (int i = 0; i < _t.size(); i++)
+		{
+			ret[i] = (TextureVo)_t.get(i);
+		}
+		return ret;
+	}
+	
+	/**
+	 * Returns a String Array of the textureIds of each of the items in the list 
+	 */
+	public String[] getIds()
+	{
+		// BTW this makes a casting error. Why?
+		// (TextureVo[])_t.toArray();
+
+		String[] a = new String[_t.size()];
+		for (int i = 0; i < _t.size(); i++)
+		{
+			a[i] = _t.get(i).textureId;
+		}
+		return a;
+	}
+}

+ 165 - 0
app/src/main/java/min3d/core/TextureManager.java

@@ -0,0 +1,165 @@
+package min3d.core;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import min3d.Min3d;
+import min3d.Shared;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+/**
+ * TextureManager is responsible for managing textures for the whole environment. 
+ * It maintains a list of id's that are mapped to the GL texture names (id's).
+ * 
+ * You add a Bitmap to the TextureManager, which adds a textureId to its list.
+ * Then, you assign one or more TextureVo's to your Object3d's using id's that 
+ * exist in the TextureManager.
+ * 
+ * Note that the _idToTextureName and _idToHasMipMap HashMaps used below
+ * don't test for exceptions. 
+ */
+public class TextureManager 
+{
+	private HashMap<String, Integer> _idToTextureName;
+	private HashMap<String, Boolean> _idToHasMipMap;
+	private static int _counter = 1000001;
+	private static int _atlasId = 0;
+	
+	
+	public TextureManager()
+	{
+		reset();
+	}
+
+	public void reset()
+	{
+		// Delete any extant textures
+
+		if (_idToTextureName != null) 
+		{
+			Set<String> s = _idToTextureName.keySet();
+			Object[] a = s.toArray(); 
+			for (int i = 0; i < a.length; i++) {
+				int glId = getGlTextureId((String)a[i]);
+				Shared.renderer().deleteTexture(glId);
+			}
+			// ...pain
+		}
+		
+		_idToTextureName = new HashMap<String, Integer>();
+		_idToHasMipMap = new HashMap<String, Boolean>();
+	}
+
+	/**
+	 * 'Uploads' a texture via OpenGL which is mapped to a textureId to the TextureManager, 
+	 * which can subsequently be used to assign textures to Object3d's. 
+	 * 
+	 * @return The textureId as added to TextureManager, which is identical to $id 
+	 */
+	public String addTextureId(Bitmap $b, String $id, boolean $generateMipMap)
+	{
+		if (_idToTextureName.containsKey($id)) throw new Error("Texture id \"" + $id + "\" already exists."); 
+
+		int glId = Shared.renderer().uploadTextureAndReturnId($b, $generateMipMap);
+
+		String s = $id;
+		_idToTextureName.put(s, glId);
+		_idToHasMipMap.put(s, $generateMipMap);
+	
+		_counter++;
+		
+		// For debugging purposes (potentially adds a lot of chatter)
+		// logContents();
+		
+		return s;
+	}
+
+	/**
+	 * Alternate signature for "addTextureId", with MIP mapping set to false by default.
+	 * Kept for API backward-compatibility. 
+	 */
+	public String addTextureId(Bitmap $b, String $id)
+	{
+		return this.addTextureId($b, $id, false);
+	}
+	
+	/**
+	 * 'Uploads' texture via OpenGL and returns an autoassigned textureId,
+	 * which can be used to assign textures to Object3d's. 
+	 */
+	public String createTextureId(Bitmap $b, boolean $generateMipMap)
+	{
+		return addTextureId($b, (_counter+""), $generateMipMap);
+	}
+	
+	/**
+	 * Deletes a textureId from the TextureManager,  
+	 * and deletes the corresponding texture from the GPU
+	 */
+	public void deleteTexture(String $textureId)
+	{
+		int glId = _idToTextureName.get($textureId);
+		Shared.renderer().deleteTexture(glId);
+		_idToTextureName.remove($textureId);
+		_idToHasMipMap.remove($textureId);
+		
+		// logContents();
+		
+		//xxx needs error check
+	}
+
+	/**
+	 * Returns a String Array of textureId's in the TextureManager 
+	 */
+	public String[] getTextureIds()
+	{
+		Set<String> set = _idToTextureName.keySet();
+		String[] a = new String[set.size()];
+		set.toArray(a);
+		return a;
+	}
+	
+	/**
+	 * Used by Renderer
+	 * 
+	 */
+	int getGlTextureId(String $textureId) /*package-private*/
+	{
+		return _idToTextureName.get($textureId);
+	}
+	
+	/**
+	 * Used by Renderer
+	 */
+	boolean hasMipMap(String $textureId) /*package-private*/
+	{
+		return _idToHasMipMap.get($textureId);
+	}
+	
+
+	public boolean contains(String $textureId)
+	{
+		return _idToTextureName.containsKey($textureId);
+	}
+	
+	
+	private String arrayToString(String[] $a)
+	{
+		String s = "";
+		for (int i = 0; i < $a.length; i++)
+		{
+			s += $a[i].toString() + " | ";
+		}
+		return s;
+	}
+	
+	private void logContents()
+	{
+		Log.v(Min3d.TAG, "TextureManager contents updated - " + arrayToString( getTextureIds() ) );		
+	}
+	
+	public String getNewAtlasId() {
+		return "atlas".concat(Integer.toString(_atlasId++));
+	}
+}

+ 137 - 0
app/src/main/java/min3d/core/UvBufferList.java

@@ -0,0 +1,137 @@
+package min3d.core;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import min3d.vos.Uv;
+
+
+public class UvBufferList 
+{
+	public static final int PROPERTIES_PER_ELEMENT = 2;
+	public static final int BYTES_PER_PROPERTY = 4;
+
+	private FloatBuffer _b;
+	private int _numElements = 0;
+	
+	public UvBufferList(FloatBuffer $b, int $size)
+	{
+		ByteBuffer bb = ByteBuffer.allocateDirect($b.limit() * BYTES_PER_PROPERTY); 
+		bb.order(ByteOrder.nativeOrder());
+		_b = bb.asFloatBuffer();
+		_b.put($b);
+		_numElements = $size;
+	}
+	
+	public UvBufferList(int $maxElements)
+	{
+		int numBytes = $maxElements * PROPERTIES_PER_ELEMENT * BYTES_PER_PROPERTY;
+		ByteBuffer bb = ByteBuffer.allocateDirect(numBytes); 
+		bb.order(ByteOrder.nativeOrder());
+		
+		_b  = bb.asFloatBuffer();
+	}
+	
+	/**
+	 * The number of items in the list. 
+	 */
+	public int size()
+	{
+		return _numElements;
+	}
+	
+	/**
+	 * The _maximum_ number of items that the list can hold, as defined on instantiation.
+	 * (Not to be confused with the Buffer's capacity)
+	 */
+	public int capacity()
+	{
+		return _b.capacity() / PROPERTIES_PER_ELEMENT;
+	}
+	
+	/**
+	 * Clear object in preparation for garbage collection
+	 */
+	public void clear()
+	{
+		_b.clear();
+	}
+	
+	public Uv getAsUv(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return new Uv( _b.get(), _b.get() );
+	}
+	
+	public void putInUv(int $index, Uv $uv)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		$uv.u = _b.get();
+		$uv.v = _b.get();
+	}
+
+	public float getPropertyU(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		return _b.get();
+	}
+	public float getPropertyV(int $index)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		return _b.get();
+	}
+	
+	//
+	
+	public void add(Uv $uv)
+	{
+		set( _numElements, $uv );
+		_numElements++;
+	}
+	
+	public void add(float $u, float $v)
+	{
+		set(_numElements, $u, $v);
+		_numElements++;
+	}
+	
+	public void set(int $index, Uv $uv)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($uv.u);
+		_b.put($uv.v);
+	}
+
+	public void set(int $index, float $u, float $v)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($u);
+		_b.put($v);
+	}
+	
+	public void setPropertyU(int $index, float $u)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT);
+		_b.put($u);
+	}
+	public void setPropertyV(int $index, float $v)
+	{
+		_b.position($index * PROPERTIES_PER_ELEMENT + 1);
+		_b.put($v);
+	}
+	
+	//
+	
+	public FloatBuffer buffer()
+	{
+		return _b;
+	}
+	
+	public UvBufferList clone()
+	{
+		_b.position(0);
+		UvBufferList c = new UvBufferList(_b, size());
+		return c;
+	}
+}

+ 183 - 0
app/src/main/java/min3d/core/Vertices.java

@@ -0,0 +1,183 @@
+package min3d.core;
+
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+
+
+public class Vertices
+{
+	private Number3dBufferList _points;
+	private UvBufferList _uvs;
+	private Number3dBufferList _normals;
+	private Color4BufferList _colors;
+	
+	private boolean _hasUvs;
+	private boolean _hasNormals;
+	private boolean _hasColors;
+	
+	
+	/**
+	 * Used by Object3d to hold the lists of vertex points, texture coordinates (UV), normals, and vertex colors. 
+	 * Use "addVertex()" to build the vertex data for the Object3d instance associated with this instance. 
+	 * 
+	 * Direct manipulation of position, UV, normal, or color data can be done directly through the associated 
+	 * 'buffer list' instances contained herein.
+	 */
+	public Vertices(int $maxElements)
+	{
+		_points = new Number3dBufferList($maxElements);
+		
+		_hasUvs = true;
+		_hasNormals = true;
+		_hasColors = true;
+		
+		if (_hasUvs) _uvs = new UvBufferList($maxElements);
+		if (_hasNormals) _normals = new Number3dBufferList($maxElements);
+		if (_hasColors) _colors = new Color4BufferList($maxElements);
+	}
+
+	/**
+	 * This version of the constructor adds 3 boolean arguments determine whether 
+	 * uv, normal, and color lists will be used by this instance.
+	 * Set to false when appropriate to save memory, increase performance. 
+	 */
+	public Vertices(int $maxElements, Boolean $useUvs, Boolean $useNormals, Boolean $useColors)
+	{
+		_points = new Number3dBufferList($maxElements);
+		
+		_hasUvs = $useUvs;
+		_hasNormals = $useNormals;
+		_hasColors = $useColors;
+		
+		if (_hasUvs) _uvs = new UvBufferList($maxElements);
+		if (_hasNormals) _normals = new Number3dBufferList($maxElements);
+		if (_hasColors) _colors = new Color4BufferList($maxElements);
+	}
+	
+	public Vertices(Number3dBufferList $points, UvBufferList $uvs, Number3dBufferList $normals,
+			Color4BufferList $colors)
+	{
+		_points = $points;
+		_uvs = $uvs;
+		_normals = $normals;
+		_colors = $colors;
+		
+		_hasUvs = _uvs != null && _uvs.size() > 0;
+		_hasNormals = _normals != null && _normals.size() > 0;
+		_hasColors = _colors != null && _colors.size() > 0;
+	}
+	
+	public int size()
+	{
+		return _points.size();
+	}
+	
+	public int capacity()
+	{
+		return _points.capacity();
+	}
+	
+	public boolean hasUvs()
+	{
+		return _hasUvs;
+	}
+
+	public boolean hasNormals()
+	{
+		return _hasNormals;
+	}
+	
+	public boolean hasColors()
+	{
+		return _hasColors;
+	}
+	
+	
+	/**
+	 * Use this to populate an Object3d's vertex data.
+	 * Return's newly added vertex's index 
+	 * 
+	 *  	If hasUvs, hasNormals, or hasColors was set to false, 
+	 * 		their corresponding arguments are just ignored.
+	 */
+	public short addVertex(
+		float $pointX, float $pointY, float $pointZ,  
+		float $textureU, float $textureV,  
+		float $normalX, float $normalY, float $normalZ,  
+		short $colorR, short $colorG, short $colorB, short $colorA)
+	{
+		_points.add($pointX, $pointY, $pointZ);
+		
+		if (_hasUvs) _uvs.add($textureU, $textureV);
+		if (_hasNormals) _normals.add($normalX, $normalY, $normalZ);
+		if (_hasColors) _colors.add($colorR, $colorG, $colorB, $colorA);
+		
+		return (short)(_points.size()-1);
+	}
+	
+	/**
+	 * More structured-looking way of adding a vertex (but potentially wasteful).
+	 * The VO's taken in as arguments are only used to read the values they hold
+	 * (no references are kept to them).  
+	 * Return's newly added vertex's index 
+	 * 
+	 * 		If hasUvs, hasNormals, or hasColors was set to false, 
+	 * 		their corresponding arguments are just ignored.
+	 */
+	public short addVertex(Number3d $point, Uv $textureUv, Number3d $normal, Color4 $color)
+	{
+		_points.add($point);
+		
+		if (_hasUvs) _uvs.add($textureUv);
+		if (_hasNormals) _normals.add($normal);
+		if (_hasColors) _colors.add($color);
+		
+		return (short)(_points.size()-1);
+	}
+	
+	public void overwriteVerts(float[] $newVerts)
+	{
+		_points.overwrite($newVerts);
+	}
+	
+	public void overwriteNormals(float[] $newNormals)
+	{
+		_normals.overwrite($newNormals);
+	}
+	
+	Number3dBufferList points() /*package-private*/
+	{
+		return _points;
+	}
+	
+	/**
+	 * List of texture coordinates
+	 */
+	UvBufferList uvs() /*package-private*/
+	{
+		return _uvs;
+	}
+	
+	/**
+	 * List of normal values 
+	 */
+	Number3dBufferList normals() /*package-private*/
+	{
+		return _normals;
+	}
+	
+	/**
+	 * List of color values
+	 */
+	Color4BufferList colors() /*package-private*/
+	{
+		return _colors;
+	}
+	
+	public Vertices clone()
+	{
+		Vertices v = new Vertices(_points.clone(), _uvs.clone(), _normals.clone(), _colors.clone());
+		return v;
+	}
+}

+ 8 - 0
app/src/main/java/min3d/interfaces/IDirtyManaged.java

@@ -0,0 +1,8 @@
+package min3d.interfaces;
+
+public interface IDirtyManaged 
+{
+	public boolean isDirty();
+	public void setDirtyFlag();
+	public void clearDirtyFlag();
+}

+ 6 - 0
app/src/main/java/min3d/interfaces/IDirtyParent.java

@@ -0,0 +1,6 @@
+package min3d.interfaces;
+
+public interface IDirtyParent 
+{
+	public void onDirty();
+}

+ 20 - 0
app/src/main/java/min3d/interfaces/IObject3dContainer.java

@@ -0,0 +1,20 @@
+package min3d.interfaces;
+
+import java.util.ArrayList;
+
+import min3d.core.Object3d;
+
+/**
+ * Using Actionscript 3 nomenclature for what are essentially "pass-thru" methods to an underlying ArrayList  
+ */
+public interface IObject3dContainer 
+{
+	public void addChild(Object3d $child);
+	public void addChildAt(Object3d $child, int $index);
+	public boolean removeChild(Object3d $child);
+	public Object3d removeChildAt(int $index);
+	public Object3d getChildAt(int $index);
+	public Object3d getChildByName(String $string);
+	public int getChildIndexOf(Object3d $o);	
+	public int numChildren();
+}

+ 39 - 0
app/src/main/java/min3d/interfaces/ISceneController.java

@@ -0,0 +1,39 @@
+package min3d.interfaces;
+
+import android.os.Handler;
+
+/**
+ * Interface to handle the initialization of the Scene 
+ * and the 'on-enter-frame' updates to the Scene (think 'model').
+ * 
+ * The RendererActivity class implements this interface.
+ * 
+ * But you could use any other class to implement this interface, 
+ * not just the Activity class.
+ *   
+ */
+public interface ISceneController 
+{
+	/**
+	 * Initialization of scene objects happens here. 
+	 * 
+	 * It is called after the GL Surface is created, which means not only at startup, 
+	 * but also when the application resumes after losing focus.
+	 * 
+	 * It would be the end-user's responsibility to save and restore state if so desired...
+	 */
+	public void initScene();
+
+	/**
+	 * Updating properties of scene objects happens here.
+	 * This is called on every frame right before the render routine.
+	 */
+	public void updateScene();
+	
+	
+	public Handler getInitSceneHandler();
+	public Runnable getInitSceneRunnable();
+	
+	public Handler getUpdateSceneHandler();
+	public Runnable getUpdateSceneRunnable();
+}

+ 114 - 0
app/src/main/java/min3d/objectPrimitives/Box.java

@@ -0,0 +1,114 @@
+package min3d.objectPrimitives;
+
+import min3d.Utils;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+
+
+/**
+ * Note how each 'face' (quad) of the box uses its own set of 4 vertices each, 
+ * rather than sharing with adjacent faces.  This allows for each face to be 
+ * texture mapped, normal'ed, and colored independently of the others. 
+ * 
+ * Object origin is center of box. 
+ */
+public class Box extends Object3dContainer
+{
+	private Color4[] _cols;
+	private float _width;
+	private float _height;
+	private float _depth;
+	
+
+	public Box(float $width, float $height, float $depth, Color4[] $sixColor4s, Boolean $useUvs, Boolean $useNormals, Boolean $useVertexColors)
+	{
+		super(4*6, 2*6, $useUvs,$useNormals,$useVertexColors);
+		
+		_width = $width;
+		_height = $height;
+		_depth = $depth;
+		
+		if ($sixColor4s != null)
+		{
+			_cols = $sixColor4s;
+		}
+		else
+		{
+			_cols = new Color4[6];
+			_cols[0] = new Color4(255,0,0,255);
+			_cols[1] = new Color4(0,255,0,255);
+			_cols[2] = new Color4(0,0,255,255);
+			_cols[3] = new Color4(255,255,0,255);
+			_cols[4] = new Color4(0,255,255,255);
+			_cols[5] = new Color4(255,0,255,255);
+		}
+		
+		make();
+	}
+	
+	public Box(float $width, float $height, float $depth, Color4[] $sixColor4s)
+	{
+		this($width,$height,$depth, $sixColor4s, true,true,true);
+	}
+	
+	public Box(float $width, float $height, float $depth, Color4 color)
+	{
+		this($width,$height,$depth, new Color4[] { color, color, color, color, color, color }, true,true,true);
+	}
+
+	public Box(float $width, float $height, float $depth)
+	{
+		this($width,$height,$depth, null,  true,true,true);
+	}
+
+	private void make()
+	{
+		float w = _width / 2f;
+		float h = _height / 2f;
+		float d = _depth / 2f;
+
+		short ul, ur, lr, ll;
+		
+		// front
+		ul = this.vertices().addVertex(-w,+h,+d,	0f,0f,	0,0,1,	_cols[0].r,_cols[0].g,_cols[0].b,_cols[0].a);
+		ur = this.vertices().addVertex(+w,+h,+d,	1f,0f,	0,0,1,	_cols[0].r,_cols[0].g,_cols[0].b,_cols[0].a);
+		lr = this.vertices().addVertex(+w,-h,+d,	1f,1f,	0,0,1,	_cols[0].r,_cols[0].g,_cols[0].b,_cols[0].a);
+		ll = this.vertices().addVertex(-w,-h,+d,	0f,1f,	0,0,1,	_cols[0].r,_cols[0].g,_cols[0].b,_cols[0].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+		
+		// right
+		ul = this.vertices().addVertex(+w,+h,+d,	0f,0f,	1,0,0,	_cols[1].r,_cols[1].g,_cols[1].b,_cols[1].a);
+		ur = this.vertices().addVertex(+w,+h,-d,	1f,0f,	1,0,0,	_cols[1].r,_cols[1].g,_cols[1].b,_cols[1].a);
+		lr = this.vertices().addVertex(+w,-h,-d,	1f,1f,	1,0,0,	_cols[1].r,_cols[1].g,_cols[1].b,_cols[1].a);
+		ll = this.vertices().addVertex(+w,-h,+d,	0f,1f,	1,0,0,	_cols[1].r,_cols[1].g,_cols[1].b,_cols[1].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+
+		// back
+		ul = this.vertices().addVertex(+w,+h,-d,	0f,0f,	0,0,-1,	_cols[2].r,_cols[2].g,_cols[2].b,_cols[2].a);
+		ur = this.vertices().addVertex(-w,+h,-d,	1f,0f,	0,0,-1,	_cols[2].r,_cols[2].g,_cols[2].b,_cols[2].a);
+		lr = this.vertices().addVertex(-w,-h,-d,	1f,1f,	0,0,-1,	_cols[2].r,_cols[2].g,_cols[2].b,_cols[2].a);
+		ll = this.vertices().addVertex(+w,-h,-d,	0f,1f,	0,0,-1,	_cols[2].r,_cols[2].g,_cols[2].b,_cols[2].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+	
+		// left
+		ul = this.vertices().addVertex(-w,+h,-d,	0f,0f,	-1,0,0,	_cols[3].r,_cols[3].g,_cols[3].b,_cols[3].a);
+		ur = this.vertices().addVertex(-w,+h,+d,	1f,0f,	-1,0,0,	_cols[3].r,_cols[3].g,_cols[3].b,_cols[3].a);
+		lr = this.vertices().addVertex(-w,-h,+d,	1f,1f,	-1,0,0,	_cols[3].r,_cols[3].g,_cols[3].b,_cols[3].a);
+		ll = this.vertices().addVertex(-w,-h,-d,	0f,1f,	-1,0,0,	_cols[3].r,_cols[3].g,_cols[3].b,_cols[3].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+		
+		// top
+		ul = this.vertices().addVertex(-w,+h,-d,	0f,0f,	0,1,0,	_cols[4].r,_cols[4].g,_cols[4].b,_cols[4].a);
+		ur = this.vertices().addVertex(+w,+h,-d,	1f,0f,	0,1,0,	_cols[4].r,_cols[4].g,_cols[4].b,_cols[4].a);
+		lr = this.vertices().addVertex(+w,+h,+d,	1f,1f,	0,1,0,	_cols[4].r,_cols[4].g,_cols[4].b,_cols[4].a);
+		ll = this.vertices().addVertex(-w,+h,+d,	0f,1f,	0,1,0,	_cols[4].r,_cols[4].g,_cols[4].b,_cols[4].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+
+		// bottom
+		ul = this.vertices().addVertex(-w,-h,+d,	0f,0f,	0,-1,0,	_cols[5].r,_cols[5].g,_cols[5].b,_cols[5].a);
+		ur = this.vertices().addVertex(+w,-h,+d,	1f,0f,	0,-1,0,	_cols[5].r,_cols[5].g,_cols[5].b,_cols[5].a);
+		lr = this.vertices().addVertex(+w,-h,-d,	1f,1f,	0,-1,0,	_cols[5].r,_cols[5].g,_cols[5].b,_cols[5].a);
+		ll = this.vertices().addVertex(-w,-h,-d,	0f,1f,	0,-1,0,	_cols[5].r,_cols[5].g,_cols[5].b,_cols[5].a);
+		Utils.addQuad(this, ul,ur,lr,ll);
+	}
+}

+ 142 - 0
app/src/main/java/min3d/objectPrimitives/HollowCylinder.java

@@ -0,0 +1,142 @@
+package min3d.objectPrimitives;
+
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Face;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+
+/**
+ * Example of a more complex programmatically-drawn object.
+ * 
+ * Unfortunately, because the object's faces share as many vertices as possible,
+ * shading is incorrect on the cylinder surfaces.
+ *  
+ * To fix would require duplicating vertices, adding new normals, 
+ * and generally reworking algorithm. Yek.
+ */
+public class HollowCylinder extends Object3dContainer 
+{
+	private double DEG = Math.PI / 180;
+	
+	private int _segs;
+	private float _radiusOuter;
+	private float _radiusInner;
+	private float _height;
+	
+
+	public HollowCylinder(float $radiusOuter, float $radiusInner, float $height, int $segs)
+	{
+		super($segs * 4, $segs * 8);
+		
+		_segs = $segs;
+		_height = $height;
+		_radiusOuter = $radiusOuter;
+		_radiusInner = $radiusInner;
+		
+		addHorizontalSurface(false, _height / +2);
+		addHorizontalSurface(true, _height / -2);
+		addVerticalSurface(true);
+		addVerticalSurface(false);
+	}
+	
+	private void addHorizontalSurface(boolean $isTopSide, float $zOffset)
+	{
+		int indexOffset = _vertices.size();
+		float step = (float)((360.0 / _segs) * DEG);
+
+		// verts
+		
+		Color4 col = $isTopSide ? new Color4(255,0,0,255) : new Color4(0,255,0,255);
+		
+		for (int i = 0; i < _segs; i++)
+		{
+			float angle = (float)i * step;
+
+			// outer 
+			float x1 		= (float) Math.sin(angle) * _radiusOuter;
+			float y1 		= (float) Math.cos(angle) * _radiusOuter;
+			float z1 		= $zOffset; 
+			Uv uv1 			= new Uv(x1,y1);
+			Number3d n1 	= new Number3d(0,0, $isTopSide ? -1 : +1);
+			this.vertices().addVertex(new Number3d(x1,y1,z1), uv1, n1, col);
+
+			// inner 
+			float x2 		= (float) Math.sin(angle) * _radiusInner;
+			float y2 		= (float) Math.cos(angle) * _radiusInner;
+			float z2 		= $zOffset; 
+			Uv uv2			= new Uv(x2,y2);
+			Number3d n2	= new Number3d(0,0, $isTopSide ? -1 : +1);
+			this.vertices().addVertex(new Number3d(x2,y2,z2), uv2, n2, col);
+		}
+		
+		// indicies
+		
+		for (int i = 2; i <= _segs; i++)
+		{
+			int a = indexOffset  +  i*2 - 3 - 1;
+			int b = indexOffset  +  i*2 - 2 - 1;
+			int c = indexOffset  +  i*2 - 1 - 1;
+			int d = indexOffset  +  i*2 - 0 - 1;
+			addQuad(a,b,c,d, $isTopSide);
+		}		
+	
+		int a = indexOffset  +  _segs*2 - 1 - 1; // ... connect last segment
+		int b = indexOffset  +  _segs*2 - 0 - 1;
+		int c = indexOffset  +  0;
+		int d = indexOffset  +  1;
+		addQuad(a,b,c,d, $isTopSide);
+	}
+	
+	// 
+	private void addVerticalSurface(boolean $isOuter)
+	{
+		int off = (int)(_vertices.size() / 2); 
+		
+		for (int i = 0; i < _segs - 1; i++)
+		{
+			int ul = i*2;
+			int bl = ul + off;
+			int ur = i*2 + 2;
+			int br = ur + off;
+			
+			if (!$isOuter) {
+				ul++;
+				bl++;
+				ur++;
+				br++;
+			}
+			addQuad(ul,bl,ur,br, $isOuter);
+		}
+		
+		int ul = (_segs-1)*2;
+		int bl = ul + off;
+		int ur = 0*2;
+		int br = ur + off;
+
+		if (!$isOuter) {
+			ul++;
+			bl++;
+			ur++;
+			br++;
+		}
+
+		addQuad(ul,bl,ur,br, $isOuter);
+	}
+	
+	private void addQuad(int ul, int bl, int ur, int br, boolean $flipped)
+	{
+		Face t;
+		
+		if (! $flipped)
+		{
+			_faces.add((short)ul,(short)bl,(short)ur);
+			_faces.add((short)bl,(short)br,(short)ur);
+		}
+		else
+		{
+			_faces.add((short)ur,(short)br,(short)ul);
+			_faces.add((short)br,(short)bl,(short)ul);
+		}
+	}
+}

+ 57 - 0
app/src/main/java/min3d/objectPrimitives/Rectangle.java

@@ -0,0 +1,57 @@
+package min3d.objectPrimitives;
+
+import min3d.Utils;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+
+public class Rectangle extends Object3dContainer
+{
+	public Rectangle(float $width, float $height, int $segsW, int $segsH)
+	{
+		this($width, $height, $segsW, $segsH, new Color4(255, 0, 0, 255));
+	}
+	
+	public Rectangle(float $width, float $height, int $segsW, int $segsH, Color4 color)
+	{
+		super(4 * $segsW * $segsH, 2 * $segsW * $segsH);
+
+		int row, col;
+
+		float w = $width / $segsW;
+		float h = $height / $segsH;
+
+		float width5 = $width/2f;
+		float height5 = $height/2f;
+		
+		// Add vertices
+		
+		for (row = 0; row <= $segsH; row++)
+		{
+			for (col = 0; col <= $segsW; col++)
+			{
+				this.vertices().addVertex(
+					(float)col*w - width5, (float)row*h - height5,0f,	
+					(float)col/(float)$segsW, 1 - (float)row/(float)$segsH,	
+					0,0,1f,	
+					color.r, color.g, color.b, color.a
+				);
+			}
+		}
+		
+		// Add faces
+		
+		int colspan = $segsW + 1;
+		
+		for (row = 1; row <= $segsH; row++)
+		{
+			for (col = 1; col <= $segsW; col++)
+			{
+				int lr = row * colspan + col;
+				int ll = lr - 1;
+				int ur = lr - colspan;
+				int ul = ur - 1;
+				Utils.addQuad(this, ul,ur,lr,ll);
+			}
+		}
+	}
+}

+ 106 - 0
app/src/main/java/min3d/objectPrimitives/SkyBox.java

@@ -0,0 +1,106 @@
+package min3d.objectPrimitives;
+
+import android.graphics.Bitmap;
+import min3d.Shared;
+import min3d.Utils;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+
+public class SkyBox extends Object3dContainer {
+	private float size;
+	private float halfSize;
+	private int quality;
+	private Color4 color;
+	private Rectangle[] faces;
+	
+	public enum Face {
+		North,
+		East,
+		South,
+		West,
+		Up,
+		Down,
+		All
+	}
+	
+	public SkyBox(float size, int quality) {
+		super(0, 0);
+		this.size = size;
+		this.halfSize = size *.5f;
+		this.quality = quality;
+		build();
+	}
+	
+	private void build() {
+		color = new Color4();
+		faces = new Rectangle[6];
+		Rectangle north = new Rectangle(size, size, quality, quality, color);
+		Rectangle east = new Rectangle(size, size, quality, quality, color);
+		Rectangle south = new Rectangle(size, size, quality, quality, color);
+		Rectangle west = new Rectangle(size, size, quality, quality, color);
+		Rectangle up = new Rectangle(size, size, quality, quality, color);
+		Rectangle down = new Rectangle(size, size, quality, quality, color);
+		
+		north.position().z = halfSize;
+		north.lightingEnabled(false);
+		
+		east.rotation().y = -90;
+		east.position().x = halfSize;
+		east.doubleSidedEnabled(true);
+		east.lightingEnabled(false);
+		
+		south.rotation().y = 180;
+		south.position().z = -halfSize;
+		south.lightingEnabled(false);
+		
+		west.rotation().y = 90;
+		west.position().x = -halfSize;
+		west.doubleSidedEnabled(true);
+		west.lightingEnabled(false);
+		
+		up.rotation().x = 90;
+		up.position().y = halfSize;
+		up.doubleSidedEnabled(true);
+		up.lightingEnabled(false);
+		
+		down.rotation().x = -90;
+		down.position().y = -halfSize;
+		down.doubleSidedEnabled(true);
+		down.lightingEnabled(false);
+		
+		faces[Face.North.ordinal()] = north;
+		faces[Face.East.ordinal()] = east;
+		faces[Face.South.ordinal()] = south;
+		faces[Face.West.ordinal()] = west;
+		faces[Face.Up.ordinal()] = up;
+		faces[Face.Down.ordinal()] = down;
+		
+		addChild(north);
+		addChild(east);
+		addChild(south);
+		addChild(west);
+		addChild(up);
+		addChild(down);
+	}
+	
+	public void addTexture(Face face, int resourceId, String id) {
+		Bitmap bitmap = Utils.makeBitmapFromResourceId(resourceId);
+		Shared.textureManager().addTextureId(bitmap, id, true);
+		bitmap.recycle();
+		addTexture(face, bitmap, id);
+	}
+	
+	public void addTexture(Face face, Bitmap bitmap, String id) {
+		if(face == Face.All)
+		{
+			for(int i=0; i<6; i++)
+			{
+				faces[i].textures().addById(id);
+			}
+		}
+		else
+		{
+			faces[face.ordinal()].textures().addById(id);
+		}
+	}
+}

+ 127 - 0
app/src/main/java/min3d/objectPrimitives/Sphere.java

@@ -0,0 +1,127 @@
+package min3d.objectPrimitives;
+
+import min3d.Utils;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+
+
+/**
+ * Creates a sphere.
+ * Vertex colors are assigned randomly across the 'latitudes' of the sphere,
+ */
+public class Sphere extends Object3dContainer
+{
+	private float _radius;
+	private int _cols;
+	private int _rows;
+	
+	
+	public Sphere(float $radius, int $columns, int $rows, Boolean $useUvs, Boolean $useNormals, Boolean $useVertexColors)
+	{
+		super(
+			($columns+1) * ($rows+1),
+			$columns * $rows * 2,
+			$useUvs,
+			$useNormals,
+			$useVertexColors
+		);
+
+		_cols = $columns;
+		_rows = $rows;
+		_radius = $radius;
+
+		build();
+	}
+
+	public Sphere(float $radius, int $columns, int $rows)
+	{
+		super(
+				($columns+1) * ($rows+1),
+				$columns * $rows * 2,
+				true,
+				true,
+				true
+			);
+
+			_cols = $columns;
+			_rows = $rows;
+			_radius = $radius;
+			
+			build();
+	} 
+	
+	public Sphere(float $radius, int $columns, int $rows, Color4 color)
+	{
+		super(
+				($columns+1) * ($rows+1),
+				$columns * $rows * 2,
+				true,
+				true,
+				true
+		);
+		defaultColor(color);
+		_cols = $columns;
+		_rows = $rows;
+		_radius = $radius;
+		
+		build();
+	}
+	
+	private void build()
+	{
+		int r, c;
+		
+		Number3d n = new Number3d();
+		Number3d pos = new Number3d();
+		Number3d posFull = new Number3d();
+
+		if( defaultColor() == null ) defaultColor(new Color4());
+		// Build vertices
+				
+		for (r = 0; r <= _rows; r++)
+		{
+			float v = (float)r / (float)_rows; // [0,1]
+			float theta1 = v * (float)Math.PI; // [0,PI]
+
+			n.setAll(0,1,0);
+			n.rotateZ(theta1); 
+
+			// each 'row' assigned random color. for the hell of it.
+			
+			for (c = 0; c <= _cols; c++)
+			{
+				float u = (float)c / (float)_cols; // [0,1]
+				float theta2 = u * (float)(Math.PI * 2f); // [0,2PI]
+				pos.setAllFrom(n);
+				pos.rotateY(theta2);
+				
+				posFull.setAllFrom(pos);
+				posFull.multiply(_radius);
+				
+				
+				this.vertices().addVertex(posFull.x,posFull.y,posFull.z,  u,v,  pos.x,pos.y,pos.z,  defaultColor().r,defaultColor().g,defaultColor().b,defaultColor().a);
+			}
+		}
+
+
+		// Add faces
+
+		int colLength = _cols + 1;
+		
+		for (r = 0; r < _rows; r++)
+		{
+			int offset = r * colLength; 
+			
+			for (c = 0; c < _cols; c++)
+			{
+				int ul = offset  +  c;
+				int ur = offset  +  c+1;
+				int br = offset  +  (int)(c + 1 + colLength);
+				int bl = offset  +  (int)(c + 0 + colLength);
+				
+				Utils.addQuad(this, ul,ur,br,bl);
+			}
+		}
+	}
+}

+ 115 - 0
app/src/main/java/min3d/objectPrimitives/Torus.java

@@ -0,0 +1,115 @@
+package min3d.objectPrimitives;
+
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+import min3d.vos.Vertex3d;
+
+/**
+ * @author Dennis Ippel
+ * @author Thomas Pfeiffer
+ * @author Tim Knip
+ *
+ * Torus primitive.
+ * This is an adaptation from Sandy's ActionScript 3 class which was adapted from Tim Knip's 
+ * ActionScript 2 class. All credits go to Tim Knip.
+ * Original sources available at: http://www.suite75.net/svn/papervision3d/tim/as2/org/papervision3d/objects/Torus.as
+ * Sandy source available at: http://code.google.com/p/sandy/source/browse/trunk/sandy/as3/trunk/src/sandy/primitive/Torus.as
+ *
+ */
+
+public class Torus extends Object3dContainer {
+	private final int MIN_SEGMENTSW = 3;
+	private final int MIN_SEGMENTSH = 2;
+	
+	private float largeRadius;
+	private float smallRadius;
+	private int segmentsW;
+	private int segmentsH;
+	
+	public Torus() {
+		this(2, 1, 12, 8, new Color4());
+	}
+	
+	public Torus(Color4 color) {
+		this(2, 1, 12, 8, color);
+	}
+	
+	public Torus(float largeRadius, float smallRadius, int segmentsW, int segmentsH) {
+		this(largeRadius, smallRadius, segmentsW, segmentsH, new Color4());
+	}
+	
+	public Torus(float largeRadius, float smallRadius, int segmentsW, int segmentsH, Color4 color) {
+		super(segmentsW * segmentsH * 2 * 3, segmentsW * segmentsH * 2);
+		this.largeRadius = largeRadius;
+		this.smallRadius = smallRadius;
+		this.segmentsW = Math.max(MIN_SEGMENTSW, segmentsW);
+		this.segmentsH = Math.max(MIN_SEGMENTSH, segmentsH);
+		this.defaultColor(color);
+		build();
+	}
+	
+	private void build()
+	{
+		float r1 = largeRadius;
+		float r2 = smallRadius;
+		int steps1 = segmentsW;
+		int steps2 = segmentsH;
+		float step1r = (float) ((2.0 * Math.PI) / steps1);
+		float step2r = (float) ((2.0 * Math.PI) / steps2);
+		float a1a = 0;
+		float a1b = step1r;
+		int vcount = 0;
+		
+		for(float s=0; s<steps1; s++, a1a=a1b, a1b+=step1r) {
+			float a2a = 0;
+			float a2b = step2r;
+			
+			for(float s2=0; s2<steps2; s2++, a2a=a2b, a2b+=step2r) {
+				Vertex3d v0 = getVertex(a1a, r1, a2a, r2);
+				Vertex3d v1 = getVertex(a1b, r1, a2a, r2);
+				Vertex3d v2 = getVertex(a1b, r1, a2b, r2);
+				Vertex3d v3 = getVertex(a1a, r1, a2b, r2);
+				
+				float ux1 = s/steps1;
+				float ux0 = (s+1)/steps1;
+				float uy0 = s2/steps2;
+				float uy1 = (s2+1)/steps2;
+
+				vertices().addVertex(v0.position, new Uv(1-ux1, uy0), v0.normal, defaultColor());
+				vertices().addVertex(v1.position, new Uv(1-ux0, uy0), v1.normal, defaultColor());
+				vertices().addVertex(v2.position, new Uv(1-ux0, uy1), v2.normal, defaultColor());
+				vertices().addVertex(v3.position, new Uv(1-ux1, uy1), v3.normal, defaultColor());
+				
+				faces().add(vcount, vcount+1, vcount+2);
+				faces().add(vcount, vcount+2, vcount+3);
+				
+				vcount += 4;
+			}
+		}
+	}
+	
+	private Vertex3d getVertex(float a1, float r1, float a2, float r2) {
+		Vertex3d vertex = new Vertex3d();
+		vertex.normal = new Number3d();
+		
+		float ca1 = (float)Math.cos(a1);
+		float sa1 = (float)Math.sin(a1);
+		float ca2 = (float)Math.cos(a2);
+		float sa2 = (float)Math.sin(a2);
+		
+		float centerX = r1 * ca1;
+		float centerZ = -r1 * sa1;
+		
+		vertex.normal.x = ca2 * ca1;
+		vertex.normal.y = sa2;
+		vertex.normal.z = -ca2 * sa1;
+		
+		vertex.position.x = centerX + r2 * vertex.normal.x;
+		vertex.position.y = r2 * vertex.normal.y;
+		vertex.position.z = centerZ + r2 * vertex.normal.z;
+				
+		return vertex;
+	}
+}

+ 398 - 0
app/src/main/java/min3d/parser/AParser.java

@@ -0,0 +1,398 @@
+package min3d.parser;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.Utils;
+import min3d.animation.AnimationObject3d;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.util.Log;
+
+/**
+ * Abstract parser class with basic parsing functionality.
+ * 
+ * @author dennis.ippel
+ *
+ */
+public abstract class AParser implements IParser {
+	protected Resources resources;
+	protected String resourceID;
+	protected String packageID;
+	protected String currentMaterialKey;
+	protected ArrayList<ParseObjectData> parseObjects;
+	protected ParseObjectData co;
+	protected boolean firstObject;
+	protected TextureAtlas textureAtlas;
+	protected ArrayList<Number3d> vertices;
+	protected ArrayList<Uv> texCoords;
+	protected ArrayList<Number3d> normals;
+	protected boolean generateMipMap;
+	protected HashMap<String, Material> materialMap;
+	
+	public AParser()
+	{
+		vertices = new ArrayList<Number3d>();
+		texCoords = new ArrayList<Uv>();
+		normals = new ArrayList<Number3d>();
+		parseObjects = new ArrayList<ParseObjectData>();
+		textureAtlas = new TextureAtlas();
+		firstObject = true;
+		materialMap = new HashMap<String, Material>();
+	}
+	
+	public AParser(Resources resources, String resourceID, Boolean generateMipMap)
+	{
+		this();
+		this.resources = resources;
+		this.resourceID = resourceID;
+		if (resourceID.indexOf(":") > -1)
+			this.packageID = resourceID.split(":")[0];
+		this.generateMipMap = generateMipMap;
+	}
+	
+	protected void cleanup()
+	{
+		parseObjects.clear();
+		textureAtlas.cleanup();
+		vertices.clear();
+		texCoords.clear();
+		normals.clear();
+	}
+	
+	/**
+	 * Override this in the concrete parser
+	 */
+	public Object3dContainer getParsedObject() {
+		return null;
+	}
+	
+	/**
+	 * Override this in the concrete parser if applicable 
+	 */
+	public AnimationObject3d getParsedAnimationObject() {
+		return null;
+	}
+
+	protected String readString(InputStream stream) throws IOException {
+		String result = new String();
+		byte inByte;
+		while ((inByte = (byte) stream.read()) != 0)
+			result += (char) inByte;
+		return result;
+	}
+
+	protected int readInt(InputStream stream) throws IOException {
+		return stream.read() | (stream.read() << 8) | (stream.read() << 16)
+				| (stream.read() << 24);
+	}
+
+	protected int readShort(InputStream stream) throws IOException {
+		return (stream.read() | (stream.read() << 8));
+	}
+
+	protected float readFloat(InputStream stream) throws IOException {
+		return Float.intBitsToFloat(readInt(stream));
+	}
+
+	/**
+	 * Override this in the concrete parser
+	 */
+	public void parse() {
+	}
+	
+
+	/**
+	 * Contains texture information. UV offsets and scaling is stored here.
+	 * This is used with texture atlases.
+	 * 
+	 * @author dennis.ippel
+	 *
+	 */
+	protected class BitmapAsset
+	{
+		/**
+		 * The texture bitmap
+		 */
+		public Bitmap bitmap;
+		/**
+		 * The texture identifier
+		 */
+		public String key;
+		/**
+		 * Resource ID
+		 */
+		public String resourceID;
+		/**
+		 * U-coordinate offset
+		 */
+		public float uOffset;
+		/**
+		 * V-coordinate offset
+		 */
+		public float vOffset;
+		/**
+		 * U-coordinate scaling value
+		 */
+		public float uScale;
+		/**
+		 * V-coordinate scaling value
+		 */
+		public float vScale;
+		public boolean useForAtlasDimensions;
+		
+		/**
+		 * Creates a new BitmapAsset object
+		 * @param bitmap
+		 * @param key
+		 */
+		public BitmapAsset(String key, String resourceID)
+		{
+			this.key = key;
+			this.resourceID = resourceID;
+			useForAtlasDimensions = false;
+		}
+	}
+	
+	/**
+	 * When a model contains per-face textures a texture atlas is created. This
+	 * combines multiple textures into one and re-calculates the UV coordinates.
+	 * 
+	 * @author dennis.ippel
+	 * 
+	 */
+	protected class TextureAtlas {
+		/**
+		 * The texture bitmaps that should be combined into one.
+		 */
+		private ArrayList<BitmapAsset> bitmaps;
+		/**
+		 * The texture atlas bitmap
+		 */
+		private Bitmap atlas;
+
+		/**
+		 * Creates a new texture atlas instance.
+		 */
+		public TextureAtlas() {
+			bitmaps = new ArrayList<BitmapAsset>();
+		}
+		private String atlasId;
+
+		/**
+		 * Adds a bitmap to the atlas
+		 * 
+		 * @param bitmap
+		 */
+		public void addBitmapAsset(BitmapAsset ba) {
+			BitmapAsset existingBA = getBitmapAssetByResourceID(ba.resourceID);
+
+			if(existingBA == null)
+			{
+				int bmResourceID = resources.getIdentifier(ba.resourceID, null, null);
+				if(bmResourceID == 0)
+				{
+					Log.d(Min3d.TAG, "Texture not found: " + ba.resourceID);
+					return;
+				}
+
+				Log.d(Min3d.TAG, "Adding texture " + ba.resourceID);
+				
+				Bitmap b = Utils.makeBitmapFromResourceId(bmResourceID);
+				ba.useForAtlasDimensions = true;
+				ba.bitmap = b;
+			}
+			else
+			{
+				ba.bitmap = existingBA.bitmap;
+			}
+
+			bitmaps.add(ba);
+		}
+		
+		public BitmapAsset getBitmapAssetByResourceID(String resourceID)
+		{
+			int numBitmaps = bitmaps.size();
+			
+			for(int i=0; i<numBitmaps; i++)
+			{
+				if(bitmaps.get(i).resourceID.equals(resourceID))
+					return bitmaps.get(i);
+			}
+			
+			return null;
+		}
+
+		/**
+		 * Generates a new texture atlas
+		 */
+		public void generate() {
+			Collections.sort(bitmaps, new BitmapHeightComparer());
+
+			if(bitmaps.size() == 0) return;
+			
+			BitmapAsset largestBitmap = bitmaps.get(0);
+			int totalWidth = 0;
+			int numBitmaps = bitmaps.size();
+			int uOffset = 0;
+			int vOffset = 0;
+
+			for (int i = 0; i < numBitmaps; i++) {
+				if(bitmaps.get(i).useForAtlasDimensions)
+					totalWidth += bitmaps.get(i).bitmap.getWidth();
+			}
+
+			atlas = Bitmap.createBitmap(totalWidth, largestBitmap.bitmap
+					.getHeight(), Config.ARGB_8888);
+
+			for (int i = 0; i < numBitmaps; i++) {
+				BitmapAsset ba = bitmaps.get(i);
+				BitmapAsset existingBA = getBitmapAssetByResourceID(ba.resourceID);				
+				
+				if(ba.useForAtlasDimensions)
+				{
+					Bitmap b = ba.bitmap;
+					int w = b.getWidth();
+					int h = b.getHeight();
+					int[] pixels = new int[w * h];
+					
+					b.getPixels(pixels, 0, w, 0, 0, w, h);
+					atlas.setPixels(pixels, 0, w, uOffset, vOffset, w, h);
+					
+					ba.uOffset = (float) uOffset / totalWidth;
+					ba.vOffset = 0;
+					ba.uScale = (float) w / (float) totalWidth;
+					ba.vScale = (float) h / (float) largestBitmap.bitmap.getHeight();
+					
+					uOffset += w;
+					b.recycle();
+				}
+				else
+				{
+					ba.uOffset = existingBA.uOffset;
+					ba.vOffset = existingBA.vOffset;
+					ba.uScale = existingBA.uScale;
+					ba.vScale = existingBA.vScale;
+				}
+			}
+			/*
+			FileOutputStream fos;
+			try {
+				fos = new FileOutputStream("/data/screenshot.png");
+				atlas.compress(Bitmap.CompressFormat.PNG, 100, fos);
+				fos.flush();
+				fos.close();
+			} catch (FileNotFoundException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+			*/
+			setId(Shared.textureManager().getNewAtlasId());
+		}
+
+		/**
+		 * Returns the generated texture atlas bitmap
+		 * 
+		 * @return
+		 */
+		public Bitmap getBitmap() {
+			return atlas;
+		}
+
+		/**
+		 * Indicates whether bitmaps have been added to the atlas.
+		 * 
+		 * @return
+		 */
+		public boolean hasBitmaps() {
+			return bitmaps.size() > 0;
+		}
+
+		/**
+		 * Compares the height of two BitmapAsset objects.
+		 * 
+		 * @author dennis.ippel
+		 * 
+		 */
+		private class BitmapHeightComparer implements Comparator<BitmapAsset> {
+			public int compare(BitmapAsset b1, BitmapAsset b2) {
+				int height1 = b1.bitmap.getHeight();
+				int height2 = b2.bitmap.getHeight();
+
+				if (height1 < height2) {
+					return 1;
+				} else if (height1 == height2) {
+					return 0;
+				} else {
+					return -1;
+				}
+			}
+		}
+		
+		/**
+		 * Returns a bitmap asset with a specified name.
+		 * 
+		 * @param materialKey
+		 * @return
+		 */
+		public BitmapAsset getBitmapAssetByName(String materialKey) {
+			int numBitmaps = bitmaps.size();
+
+			for (int i = 0; i < numBitmaps; i++) {
+				if (bitmaps.get(i).key.equals(materialKey))
+					return bitmaps.get(i);
+			}
+
+			return null;
+		}
+		
+		public void cleanup()
+		{
+			int numBitmaps = bitmaps.size();
+
+			for (int i = 0; i < numBitmaps; i++) {
+				bitmaps.get(i).bitmap.recycle();
+			}
+			
+			if(atlas != null) atlas.recycle();
+			bitmaps.clear();
+			vertices.clear();
+			texCoords.clear();
+			normals.clear();
+		}
+
+		public void setId(String newAtlasId) {
+			atlasId = newAtlasId;			
+		}
+
+		public String getId() {
+			return atlasId;
+		}
+	}
+	
+	protected class Material {
+		public String name;
+		public String diffuseTextureMap;
+		public Color4 diffuseColor;
+
+		public Material(String name) {
+			this.name = name;
+		}
+	}
+}

+ 27 - 0
app/src/main/java/min3d/parser/IParser.java

@@ -0,0 +1,27 @@
+package min3d.parser;
+
+import min3d.animation.AnimationObject3d;
+import min3d.core.Object3dContainer;
+
+/**
+ * Interface for 3D object parsers
+ * 
+ * @author dennis.ippel
+ *
+ */
+public interface IParser {
+	/**
+	 * Start parsing the 3D object
+	 */
+	public void parse();
+	/**
+	 * Returns the parsed object
+	 * @return
+	 */
+	public Object3dContainer getParsedObject();
+	/**
+	 * Returns the parsed animation object
+	 * @return
+	 */
+	public AnimationObject3d getParsedAnimationObject();
+}

+ 157 - 0
app/src/main/java/min3d/parser/LittleEndianDataInputStream.java

@@ -0,0 +1,157 @@
+package min3d.parser;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Taken from http://www.peterfranza.com/2008/09/26/little-endian-input-stream/
+ * @author dennis.ippel
+ *
+ */
+public class LittleEndianDataInputStream extends InputStream implements DataInput {
+ 
+	public LittleEndianDataInputStream(InputStream in) {
+		this.in = in;
+		this.d = new DataInputStream(in);
+		w = new byte[8];
+	}
+ 
+	public int available() throws IOException {
+		return d.available();
+	}
+ 
+ 
+	public final short readShort() throws IOException
+	{
+		d.readFully(w, 0, 2);
+		return (short)(
+				(w[1]&0xff) << 8 |
+				(w[0]&0xff));
+	}
+	
+	 public String readString(int length) throws IOException {
+	        if (length == 0) {
+	            return null;
+	        }
+
+	        byte[] b = new byte[length];
+	        d.readFully(b);
+
+	        return new String(b, "US-ASCII");
+	    }
+
+
+ 
+	/**
+	 * Note, returns int even though it reads a short.
+	 */
+	 public final int readUnsignedShort() throws IOException
+	 {
+		 d.readFully(w, 0, 2);
+		 return (
+				 (w[1]&0xff) << 8 |
+				 (w[0]&0xff));
+	 }
+ 
+	 /**
+	  * like DataInputStream.readChar except little endian.
+	  */
+	 public final char readChar() throws IOException
+	 {
+		 d.readFully(w, 0, 2);
+		 return (char) (
+				 (w[1]&0xff) << 8 |
+				 (w[0]&0xff));
+	 }
+ 
+	 /**
+	  * like DataInputStream.readInt except little endian.
+	  */
+	 public final int readInt() throws IOException
+	 {
+		 d.readFully(w, 0, 4);
+		 return
+		 (w[3])      << 24 |
+		 (w[2]&0xff) << 16 |
+		 (w[1]&0xff) <<  8 |
+		 (w[0]&0xff);
+	 }
+ 
+	 /**
+	  * like DataInputStream.readLong except little endian.
+	  */
+	 public final long readLong() throws IOException
+	 {
+		 d.readFully(w, 0, 8);
+		 return
+		 (long)(w[7])      << 56 | 
+		 (long)(w[6]&0xff) << 48 |
+		 (long)(w[5]&0xff) << 40 |
+		 (long)(w[4]&0xff) << 32 |
+		 (long)(w[3]&0xff) << 24 |
+		 (long)(w[2]&0xff) << 16 |
+		 (long)(w[1]&0xff) <<  8 |
+		 (long)(w[0]&0xff);
+	 }
+ 
+	 public final float readFloat() throws IOException {
+		 return Float.intBitsToFloat(readInt());
+	 }
+ 
+	 public final double readDouble() throws IOException {
+		 return Double.longBitsToDouble(readLong());
+	 }
+ 
+	 public final int read(byte b[], int off, int len) throws IOException {
+		 return in.read(b, off, len);
+	 }
+ 
+	 public final void readFully(byte b[]) throws IOException {
+		 d.readFully(b, 0, b.length);
+	 }
+ 
+	 public final void readFully(byte b[], int off, int len) throws IOException {
+		 d.readFully(b, off, len);
+	 }
+ 
+	 public final int skipBytes(int n) throws IOException {
+		 return d.skipBytes(n);
+	 }
+ 
+	 public final boolean readBoolean() throws IOException {
+		 return d.readBoolean();
+	 }
+ 
+	 public final byte readByte() throws IOException {
+		 return d.readByte();
+	 }
+ 
+	 public int read() throws IOException {
+		 return in.read();
+	 }
+ 
+	 public final int readUnsignedByte() throws IOException {
+		 return d.readUnsignedByte();
+	 }
+ 
+	 @Deprecated
+	 public final String readLine() throws IOException {
+		 return d.readLine();
+	 }
+ 
+	 public final String readUTF() throws IOException {
+		 return d.readUTF();
+	 }
+ 
+	 public final void close() throws IOException {
+		 d.close();
+	 }
+ 
+	 private DataInputStream d; // to get at high level readFully methods of
+	 // DataInputStream
+	 private InputStream in; // to get at the low-level read methods of
+	 // InputStream
+	 private byte w[]; // work array for buffering input
+}

+ 239 - 0
app/src/main/java/min3d/parser/MD2Parser.java

@@ -0,0 +1,239 @@
+package min3d.parser;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.animation.AnimationObject3d;
+import min3d.animation.KeyFrame;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class MD2Parser extends AParser implements IParser {
+	private MD2Header header;
+	private String currentTextureName;
+	private KeyFrame[] frames;
+
+	public MD2Parser(Resources resources, String resourceID, boolean generateMipMap) {
+		super(resources, resourceID, generateMipMap);
+	}
+
+	@Override
+	public AnimationObject3d getParsedAnimationObject() {
+		Log.d(Min3d.TAG, "Start object creation");
+		Bitmap texture = null;
+		AnimationObject3d animObj;
+
+		if (textureAtlas.hasBitmaps()) {
+			textureAtlas.generate();
+			texture = textureAtlas.getBitmap();
+			Shared.textureManager().addTextureId(texture, textureAtlas.getId(), generateMipMap);
+		}
+
+		Log.d(Min3d.TAG, "Creating object " + co.name);
+		animObj = co.getParsedObject(textureAtlas, materialMap, frames);
+
+		if (textureAtlas.hasBitmaps()) {
+			if (texture != null)
+				texture.recycle();
+		}
+		Log.d(Min3d.TAG, "Object creation finished");
+
+		super.cleanup();
+
+		return animObj;
+	}
+
+	@Override
+	public void parse() {
+		InputStream fileIn = resources.openRawResource(resources.getIdentifier(
+				resourceID, null, null));
+		BufferedInputStream stream = new BufferedInputStream(fileIn);
+
+		co = new ParseObjectData();
+		header = new MD2Header();
+
+		Log.d(Min3d.TAG, "Start parsing MD2 file");
+		try {
+			header.parse(stream);
+			frames = new KeyFrame[header.numFrames];
+			byte[] bytes = new byte[header.offsetEnd - 68];
+			stream.read(bytes);
+			getMaterials(stream, bytes);
+			getTexCoords(stream, bytes);
+			getFrames(stream, bytes);
+			getTriangles(stream, bytes);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	private void getMaterials(BufferedInputStream stream, byte[] bytes)
+			throws IOException {
+		ByteArrayInputStream ba = new ByteArrayInputStream(bytes,
+				header.offsetSkins - 68, bytes.length - header.offsetSkins);
+		LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
+
+		for (int i = 0; i < header.numSkins; i++) {
+			String skinPath = is.readString(64);
+			StringBuffer texture = new StringBuffer(packageID);
+			texture.append(":drawable/");
+
+			skinPath = skinPath.substring(skinPath.lastIndexOf("/") + 1,
+					skinPath.length());
+			StringBuffer textureName = new StringBuffer(skinPath.toLowerCase());
+			int dotIndex = textureName.lastIndexOf(".");
+			if (dotIndex > -1)
+				texture.append(textureName.substring(0, dotIndex));
+			else
+				texture.append(textureName);
+
+			currentTextureName = texture.toString();
+			textureAtlas.addBitmapAsset(new BitmapAsset(currentTextureName,
+					currentTextureName));
+		}
+	}
+
+	private void getTexCoords(BufferedInputStream stream, byte[] bytes)
+			throws IOException {
+		ByteArrayInputStream ba = new ByteArrayInputStream(bytes,
+				header.offsetTexCoord - 68, bytes.length
+						- header.offsetTexCoord);
+		LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
+
+		for (int i = 0; i < header.numTexCoord; i++) {
+			co.texCoords.add(new Uv((float)is.readShort() / (float)header.skinWidth, (float)is.readShort() / (float)header.skinHeight));
+		}
+	}
+
+	private void getFrames(BufferedInputStream stream, byte[] bytes)
+			throws IOException {
+		ByteArrayInputStream ba = new ByteArrayInputStream(bytes,
+				header.offsetFrames - 68, bytes.length - header.offsetFrames);
+		LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
+		ArrayList<Number3d> firstFrameVerts = new ArrayList<Number3d>();
+
+		for (int i = 0; i < header.numFrames; i++) {
+			float scaleX = is.readFloat();
+			float scaleY = is.readFloat();
+			float scaleZ = is.readFloat();
+			float translateX = is.readFloat();
+			float translateY = is.readFloat();
+			float translateZ = is.readFloat();
+			String name = is.readString(16);
+			
+			if(name.indexOf("_") > 0)
+				name = name.subSequence(0, name.lastIndexOf("_")).toString();
+			else
+				name = name.substring(0, 6).replaceAll("[0-9]{1,2}$", "");
+			
+			Log.d(Min3d.TAG, "frame name: " + name);
+			float vertices[] = new float[header.numVerts * 3];
+			int index = 0;
+
+			for (int j = 0; j < header.numVerts; j++) {
+				vertices[index++] = scaleX * is.readUnsignedByte() + translateX;
+				vertices[index++] = scaleY * is.readUnsignedByte() + translateY;
+				vertices[index++] = scaleZ * is.readUnsignedByte() + translateZ;
+				
+				int normalIndex = is.readUnsignedByte();
+				if (i == 0)
+					co.vertices.add(new Number3d(vertices[index - 3],
+							vertices[index - 2], vertices[index - 1]));
+			}
+
+			frames[i] = new KeyFrame(name, vertices);
+		}
+	}
+
+	private void getTriangles(BufferedInputStream stream, byte[] bytes)
+			throws IOException {
+		ByteArrayInputStream ba = new ByteArrayInputStream(bytes,
+				header.offsetTriangles - 68, bytes.length
+						- header.offsetTriangles);
+		LittleEndianDataInputStream is = new LittleEndianDataInputStream(ba);
+		int[] indices = new int[header.numTriangles*3];
+		int index = 0;
+
+		for (int i = 0; i < header.numTriangles; i++) {
+			int[] vertexIDs = new int[3];
+			int[] uvIDS = new int[3];
+
+			indices[index+2] = vertexIDs[2] = is.readUnsignedShort();
+			indices[index+1] = vertexIDs[1] = is.readUnsignedShort();
+			indices[index] = vertexIDs[0] = is.readUnsignedShort();
+			index += 3;
+			uvIDS[2] = is.readUnsignedShort();
+			uvIDS[1] = is.readUnsignedShort();
+			uvIDS[0] = is.readUnsignedShort();
+
+			ParseObjectFace f = new ParseObjectFace();
+			f.v = vertexIDs;
+			f.uv = uvIDS;
+			f.hasn = f.hasuv = true;
+			f.faceLength = 3;
+			f.materialKey = currentTextureName;
+			co.numFaces++;
+			co.faces.add(f);
+			co.calculateFaceNormal(f);
+		}
+		
+		for(int j=0; j<header.numFrames; j++)
+		{
+			frames[j].setIndices(indices);
+		}
+	}
+
+	private class MD2Header {
+		public int id;
+		public int version;
+		public int skinWidth;
+		public int skinHeight;
+		public int frameSize;
+		public int numSkins;
+		public int numVerts;
+		public int numTexCoord;
+		public int numTriangles;
+		public int numGLCommands;
+		public int numFrames;
+		public int offsetSkins;
+		public int offsetTexCoord;
+		public int offsetTriangles;
+		public int offsetFrames;
+		public int offsetGLCommands;
+		public int offsetEnd;
+
+		public void parse(InputStream stream) throws Exception {
+			id = readInt(stream);
+			version = readInt(stream);
+
+			if (id != 844121161 || version != 8)
+				throw new Exception("This is not a valid MD2 file.");
+
+			skinWidth = readInt(stream);
+			skinHeight = readInt(stream);
+			frameSize = readInt(stream);
+
+			numSkins = readInt(stream);
+			numVerts = readInt(stream);
+			numTexCoord = readInt(stream);
+			numTriangles = readInt(stream);
+			numGLCommands = readInt(stream);
+			numFrames = readInt(stream);
+
+			offsetSkins = readInt(stream);
+			offsetTexCoord = readInt(stream);
+			offsetTriangles = readInt(stream);
+			offsetFrames = readInt(stream);
+			offsetGLCommands = readInt(stream);
+			offsetEnd = readInt(stream);
+		}
+	}
+}

+ 230 - 0
app/src/main/java/min3d/parser/Max3DSParser.java

@@ -0,0 +1,230 @@
+package min3d.parser;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.core.Object3dContainer;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+public class Max3DSParser extends AParser implements IParser {
+	private final int IDENTIFIER_3DS = 0x4D4D;
+	private final int MESH_BLOCK = 0x3D3D;
+	private final int OBJECT_BLOCK = 0x4000;
+	private final int TRIMESH = 0x4100;
+	private final int TRI_MATERIAL = 0x4130;
+	private final int VERTICES = 0x4110;
+	private final int FACES = 0x4120;
+	private final int TEXCOORD = 0x4140;
+	private final int TEX_MAP = 0xA200;
+	private final int TEX_NAME = 0xA000;
+	private final int TEX_FILENAME = 0xA300;
+	private final int MATERIAL = 0xAFFF;
+
+	private int chunkID;
+	private int chunkEndOffset;
+	private boolean endReached;
+	private String currentObjName;
+
+	public Max3DSParser(Resources resources, String resourceID, boolean generateMipMap) {
+		super(resources, resourceID, generateMipMap);
+	}
+
+	@Override
+	public void parse() {
+		InputStream fileIn = resources.openRawResource(resources.getIdentifier(
+				resourceID, null, null));
+		BufferedInputStream stream = new BufferedInputStream(fileIn);
+		
+		Log.d(Min3d.TAG, "Start parsing object");
+		
+		co = new ParseObjectData();
+		parseObjects.add(co);
+
+		try {
+			readHeader(stream);
+			if(chunkID != IDENTIFIER_3DS)
+			{
+				Log.d(Min3d.TAG, "Not a valid .3DS file!");
+				return;
+			}
+			else
+			{
+				Log.d(Min3d.TAG, "Found a valid .3DS file");
+			}
+			while(!endReached)
+			{
+				readChunk(stream);
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		
+		Log.d(Min3d.TAG, "End parsing object");
+	}
+
+	private void readHeader(InputStream stream) throws IOException {
+		chunkID = readShort(stream);
+		chunkEndOffset = readInt(stream);
+		endReached = chunkID < 0;
+	}
+
+	private void readChunk(InputStream stream) throws IOException {
+		readHeader(stream);
+
+		switch (chunkID) {
+		case MESH_BLOCK:
+			break;
+		case OBJECT_BLOCK:
+			currentObjName = readString(stream);
+			Log.d(Min3d.TAG, "Parsing object " + currentObjName);
+			break;
+		case TRIMESH:
+			if(firstObject)
+			{
+				co.name = currentObjName;
+				firstObject = false;
+			}
+			else
+			{
+				co = new ParseObjectData();
+				co.name = currentObjName;
+				parseObjects.add(co);
+			}
+			break;
+		case VERTICES:
+			readVertices(stream);
+			break;
+		case FACES:
+			readFaces(stream);
+			break;
+		case TEXCOORD:
+			readTexCoords(stream);
+			break;
+		case TEX_NAME:
+			currentMaterialKey = readString(stream);
+			break;
+		case TEX_FILENAME:
+			String fileName = readString(stream);
+			StringBuffer texture = new StringBuffer(packageID);
+			texture.append(":drawable/");
+
+			StringBuffer textureName = new StringBuffer(fileName.toLowerCase());
+			int dotIndex = textureName.lastIndexOf(".");
+			if (dotIndex > -1)
+				texture.append(textureName.substring(0, dotIndex));
+			else
+				texture.append(textureName);
+			
+			textureAtlas.addBitmapAsset(new BitmapAsset(currentMaterialKey, texture.toString()));
+			break;
+		case TRI_MATERIAL:
+			String materialName = readString(stream);
+			int numFaces = readShort(stream);
+
+			for(int i=0; i<numFaces; i++)
+			{
+				int faceIndex = readShort(stream);
+				co.faces.get(faceIndex).materialKey = materialName;
+			}
+			break;
+		case MATERIAL:
+			break;
+		case TEX_MAP:
+			break;
+		default:
+			skipRead(stream);
+		}
+	}
+	
+	private void skipRead(InputStream stream) throws IOException
+	{
+		for(int i=0; (i<chunkEndOffset - 6) && !endReached; i++)
+		{
+			endReached = stream.read() < 0;
+		}
+	}
+	
+	private void readVertices(InputStream buffer) throws IOException {
+        float x, y, z, tmpy;
+        int numVertices = readShort(buffer);
+
+        for (int i = 0; i < numVertices; i++) {
+            x = readFloat(buffer);
+            y = readFloat(buffer);
+            z = readFloat(buffer);
+            tmpy = y;
+            y = z;
+            z = -tmpy;
+            
+            co.vertices.add(new Number3d(x, y, z));
+        }
+    }
+	
+	private void readFaces(InputStream buffer) throws IOException {
+        int triangles = readShort(buffer);
+        for (int i = 0; i < triangles; i++) {
+            int[] vertexIDs = new int[3];
+            vertexIDs[0] = readShort(buffer);
+            vertexIDs[1] = readShort(buffer);
+            vertexIDs[2] = readShort(buffer);
+            readShort(buffer);
+            ParseObjectFace face = new ParseObjectFace();
+            face.v = vertexIDs;
+            face.uv = vertexIDs;
+            face.faceLength = 3;
+            face.hasuv = true;
+            co.numFaces++;
+            co.faces.add(face);
+            
+            co.calculateFaceNormal(face);
+        }
+    }
+	
+	private void readTexCoords(InputStream buffer) throws IOException {
+        int numVertices = readShort(buffer);
+
+        for (int i = 0; i < numVertices; i++) {
+            Uv uv = new Uv();
+            uv.u = readFloat(buffer);
+            uv.v = readFloat(buffer) * -1f;
+            co.texCoords.add(uv);
+        }
+    }
+	
+	public Object3dContainer getParsedObject() {
+		Log.d(Min3d.TAG, "Start object creation");
+		Object3dContainer obj = new Object3dContainer(0, 0);
+		int numObjects = parseObjects.size();
+		Bitmap texture = null;
+
+		if(textureAtlas.hasBitmaps())
+		{
+			textureAtlas.generate();
+			texture = textureAtlas.getBitmap();
+			Shared.textureManager().addTextureId(texture, textureAtlas.getId(), generateMipMap);
+		}
+		
+		for (int i = 0; i < numObjects; i++) {
+			ParseObjectData o = parseObjects.get(i);
+			Log.d(Min3d.TAG, "Creating object " + o.name);
+			obj.addChild(o.getParsedObject(materialMap, textureAtlas));
+		}
+		
+		if(textureAtlas.hasBitmaps())
+		{
+			if(texture != null) texture.recycle();
+		}
+		Log.d(Min3d.TAG, "Object creation finished");
+		
+		super.cleanup();
+		
+		return obj;
+	}
+}

+ 265 - 0
app/src/main/java/min3d/parser/ObjParser.java

@@ -0,0 +1,265 @@
+package min3d.parser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import app.brest.testmin3d.R;
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.Utils;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+/**
+ * Parses Wavefront OBJ files. Basic version, this is still a work in progress!
+ * 
+ * TODO: proper error handling TODO: handle multiple objects TODO: handle groups
+ * TODO: a lot more :-) *
+ * 
+ * @author dennis.ippel
+ * @see http://en.wikipedia.org/wiki/Obj
+ * 
+ */
+
+public class ObjParser extends AParser implements IParser {
+	private final String VERTEX = "v";
+	private final String FACE = "f";
+	private final String TEXCOORD = "vt";
+	private final String NORMAL = "vn";
+	private final String OBJECT = "o";
+	private final String MATERIAL_LIB = "mtllib";
+	private final String USE_MATERIAL = "usemtl";
+	private final String NEW_MATERIAL = "newmtl";
+	private final String DIFFUSE_COLOR = "Kd";
+	private final String DIFFUSE_TEX_MAP = "map_Kd";
+
+	/**
+	 * Creates a new OBJ parser instance
+	 * 
+	 * @param resources
+	 * @param resourceID
+	 */
+	public ObjParser(Resources resources, String resourceID, boolean generateMipMap) {
+		super(resources, resourceID, generateMipMap);
+		System.out.println("Parser:"+resourceID);
+		System.err.println("Parser:"+resourceID);
+	}
+
+	@Override
+	public void parse() {
+		long startTime = Calendar.getInstance().getTimeInMillis();
+
+		InputStream fileIn = resources.openRawResource(resources.getIdentifier(
+				resourceID, null, null));
+		BufferedReader buffer = new BufferedReader(
+				new InputStreamReader(fileIn));
+		String line;
+		co = new ParseObjectData(vertices, texCoords, normals);
+		parseObjects.add(co);
+
+		Log.d(Min3d.TAG, "Start parsing object " + resourceID);
+		Log.d(Min3d.TAG, "Start time " + startTime);
+
+		try {
+			while ((line = buffer.readLine()) != null) {
+				// remove duplicate whitespace
+				// line = line.replaceAll("\\s+", " ");
+				// String[] parts = line.split(" ");
+				StringTokenizer parts = new StringTokenizer(line, " ");
+				int numTokens = parts.countTokens();
+				if (numTokens == 0)
+					continue;
+				String type = parts.nextToken();
+
+				if (type.equals(VERTEX)) {
+					Number3d vertex = new Number3d();
+					vertex.x = Float.parseFloat(parts.nextToken());
+					vertex.y = Float.parseFloat(parts.nextToken());
+					vertex.z = Float.parseFloat(parts.nextToken());
+					vertices.add(vertex);
+				} else if (type.equals(FACE)) {
+					if (numTokens == 4) {
+						co.numFaces++;
+						co.faces.add(new ObjFace(line, currentMaterialKey, 3));
+					} else if (numTokens == 5) {
+						co.numFaces += 2;
+						co.faces.add(new ObjFace(line, currentMaterialKey, 4));
+					}
+				} else if (type.equals(TEXCOORD)) {
+					Uv texCoord = new Uv();
+					texCoord.u = Float.parseFloat(parts.nextToken());
+					texCoord.v = Float.parseFloat(parts.nextToken()) * -1f;
+					texCoords.add(texCoord);
+				} else if (type.equals(NORMAL)) {
+					Number3d normal = new Number3d();
+					normal.x = Float.parseFloat(parts.nextToken());
+					normal.y = Float.parseFloat(parts.nextToken());
+					normal.z = Float.parseFloat(parts.nextToken());
+					normals.add(normal);
+				} else if (type.equals(MATERIAL_LIB)) {
+					readMaterialLib(parts.nextToken());
+				} else if (type.equals(USE_MATERIAL)) {
+					currentMaterialKey = parts.nextToken();
+				} else if (type.equals(OBJECT)) {
+					String objName = parts.hasMoreTokens() ? parts.nextToken() : ""; 
+					if(firstObject)
+					{
+						Log.d(Min3d.TAG, "Create object " + objName);
+						co.name = objName;
+						firstObject = false;
+					}
+					else
+					{
+						Log.d(Min3d.TAG, "Create object " + objName);
+						co = new ParseObjectData(vertices, texCoords, normals);
+						co.name = objName;
+						parseObjects.add(co);
+					}
+				}
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+		long endTime = Calendar.getInstance().getTimeInMillis();
+		Log.d(Min3d.TAG, "End time " + (endTime - startTime));
+	}
+
+	public Object3dContainer getParsedObject() {
+		Log.d(Min3d.TAG, "Start object creation");
+		Object3dContainer obj = new Object3dContainer(0, 0);
+		int numObjects = parseObjects.size();
+		Bitmap texture = null;
+
+		if(textureAtlas.hasBitmaps())
+		{
+			textureAtlas.generate();
+			texture = textureAtlas.getBitmap();
+			Shared.textureManager().addTextureId(texture, textureAtlas.getId(), generateMipMap);
+		}
+		
+		for (int i = 0; i < numObjects; i++) {
+			ParseObjectData o = parseObjects.get(i);
+			Log.d(Min3d.TAG, "Creating object " + o.name);
+			obj.addChild(o.getParsedObject(materialMap, textureAtlas));
+		}
+		
+		if(textureAtlas.hasBitmaps())
+		{
+			if(texture != null) texture.recycle();
+		}
+		Log.d(Min3d.TAG, "Object creation finished");
+		
+		cleanup();
+		
+		return obj;
+	}
+
+	private void readMaterialLib(String libID) {
+		StringBuffer resourceID = new StringBuffer(packageID);
+		StringBuffer libIDSbuf = new StringBuffer(libID);
+		int dotIndex = libIDSbuf.lastIndexOf(".");
+		if (dotIndex > -1)
+			libIDSbuf = libIDSbuf.replace(dotIndex, dotIndex + 1, "_");
+
+		resourceID.append(":raw/");
+		resourceID.append(libIDSbuf.toString());
+
+		InputStream fileIn = resources.openRawResource(resources.getIdentifier(
+				resourceID.toString(), null, null));
+		BufferedReader buffer = new BufferedReader(
+				new InputStreamReader(fileIn));
+		String line;
+		String currentMaterial = "";
+
+		try {
+			while ((line = buffer.readLine()) != null) {
+				String[] parts = line.split(" ");
+				if (parts.length == 0)
+					continue;
+				String type = parts[0];
+
+				if (type.equals(NEW_MATERIAL)) {
+					if (parts.length > 1) {
+						currentMaterial = parts[1];
+						materialMap.put(currentMaterial, new Material(
+								currentMaterial));
+					}
+				} else if(type.equals(DIFFUSE_COLOR) && !type.equals(DIFFUSE_TEX_MAP)) {
+					Color4 diffuseColor = new Color4(Float.parseFloat(parts[1]) * 255.0f, Float.parseFloat(parts[2]) * 255.0f, Float.parseFloat(parts[3]) * 255.0f, 255.0f);
+					materialMap.get(currentMaterial).diffuseColor = diffuseColor;
+				} else if (type.equals(DIFFUSE_TEX_MAP)) {
+					if (parts.length > 1) {
+						materialMap.get(currentMaterial).diffuseTextureMap = parts[1];
+						StringBuffer texture = new StringBuffer(packageID);
+						texture.append(":drawable/");
+						
+						StringBuffer textureName = new StringBuffer(parts[1]);
+						dotIndex = textureName.lastIndexOf(".");
+						if (dotIndex > -1)
+							texture.append(textureName.substring(0, dotIndex));
+						else
+							texture.append(textureName);
+						
+						int bmResourceID = resources.getIdentifier(texture
+								.toString(), null, null);
+						Bitmap b = Utils.makeBitmapFromResourceId(bmResourceID);
+						textureAtlas.addBitmapAsset(new BitmapAsset(currentMaterial, texture.toString()));
+					}
+				}
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	protected void cleanup() {
+		super.cleanup();
+		materialMap.clear();
+	}
+
+	private class ObjFace extends ParseObjectFace {
+		public ObjFace(String line, String materialKey, int faceLength) {
+			super();
+			this.materialKey = materialKey;
+			this.faceLength = faceLength;
+			boolean emptyVt = line.indexOf("//") > -1;
+			if(emptyVt) line = line.replace("//", "/");
+			StringTokenizer parts = new StringTokenizer(line);
+			parts.nextToken();
+			StringTokenizer subParts = new StringTokenizer(parts.nextToken(), "/");
+			int partLength = subParts.countTokens();
+			hasuv = partLength >= 2 && !emptyVt;
+			hasn = partLength == 3 || (partLength == 2 && emptyVt);
+
+			v = new int[faceLength];
+			if (hasuv)
+				uv = new int[faceLength];
+			if (hasn)
+				n = new int[faceLength];
+
+			for (int i = 1; i < faceLength + 1; i++) {
+				if (i > 1)
+					subParts = new StringTokenizer(parts.nextToken(), "/");
+
+				int index = i - 1;
+				v[index] = (short) (Short.parseShort(subParts.nextToken()) - 1);
+				if (hasuv)
+					uv[index] = (short) (Short.parseShort(subParts.nextToken()) - 1);
+				if (hasn)
+					n[index] = (short) (Short.parseShort(subParts.nextToken()) - 1);
+			}
+		}
+	}
+}

+ 165 - 0
app/src/main/java/min3d/parser/ParseObjectData.java

@@ -0,0 +1,165 @@
+package min3d.parser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.util.Log;
+
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.animation.AnimationObject3d;
+import min3d.animation.KeyFrame;
+import min3d.core.Object3d;
+import min3d.parser.AParser.BitmapAsset;
+import min3d.parser.AParser.Material;
+import min3d.parser.AParser.TextureAtlas;
+import min3d.vos.Color4;
+import min3d.vos.Face;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+
+public class ParseObjectData {
+	protected ArrayList<ParseObjectFace> faces;
+	protected int numFaces = 0;
+	protected ArrayList<Number3d> vertices;
+	protected ArrayList<Uv> texCoords;
+	protected ArrayList<Number3d> normals;
+	
+	public String name;
+	
+	public ParseObjectData()
+	{
+		this.vertices = new ArrayList<Number3d>();
+		this.texCoords = new ArrayList<Uv>();
+		this.normals = new ArrayList<Number3d>();
+		this.name = "";
+		faces = new ArrayList<ParseObjectFace>();
+	}
+
+	public ParseObjectData(ArrayList<Number3d> vertices, ArrayList<Uv> texCoords, ArrayList<Number3d> normals)
+	{
+		this.vertices = vertices;
+		this.texCoords = texCoords;
+		this.normals = normals;
+		this.name = "";
+		faces = new ArrayList<ParseObjectFace>();
+	}
+	
+	public AnimationObject3d getParsedObject(TextureAtlas textureAtlas, HashMap<String, Material> materialMap, KeyFrame[] frames)
+	{
+		AnimationObject3d obj = new AnimationObject3d(numFaces * 3, numFaces, frames.length);
+		obj.name(name);
+		obj.setFrames(frames);
+		
+		parseObject(obj, materialMap, textureAtlas);
+
+		return obj;
+	}
+	
+	public Object3d getParsedObject(HashMap<String, Material> materialMap, TextureAtlas textureAtlas) {
+		Object3d obj = new Object3d(numFaces * 3, numFaces);
+		obj.name(name);
+		
+		parseObject(obj, materialMap, textureAtlas);
+		
+		return obj;
+	}
+	
+	private void parseObject(Object3d obj, HashMap<String, Material> materialMap, TextureAtlas textureAtlas)
+	{
+		int numFaces = faces.size();
+		int faceIndex = 0;
+		boolean hasBitmaps = textureAtlas.hasBitmaps();
+
+		for (int i = 0; i < numFaces; i++) {
+			ParseObjectFace face = faces.get(i);
+			BitmapAsset ba = textureAtlas
+					.getBitmapAssetByName(face.materialKey);
+
+			for (int j = 0; j < face.faceLength; j++) {
+				Number3d newVertex = vertices.get(face.v[j]);
+				
+				Uv newUv = face.hasuv ? texCoords.get(face.uv[j]).clone()
+						: new Uv();
+				Number3d newNormal = face.hasn ? normals.get(face.n[j])
+						: new Number3d();
+				Material material = materialMap.get(face.materialKey);
+				
+				Color4 newColor = new Color4(255, 255, 0, 255);
+				if(material != null && material.diffuseColor != null)
+				{
+					newColor.r = material.diffuseColor.r;
+					newColor.g = material.diffuseColor.g;
+					newColor.b = material.diffuseColor.b;
+					newColor.a = material.diffuseColor.a;
+				}
+
+				if(hasBitmaps && (ba != null))
+				{
+					newUv.u = ba.uOffset + newUv.u * ba.uScale;
+					newUv.v = ba.vOffset + ((newUv.v + 1) * ba.vScale) - 1;
+				}
+				obj.vertices().addVertex(newVertex, newUv, newNormal, newColor);
+			}
+
+			if (face.faceLength == 3) {
+				obj.faces().add(
+						new Face(faceIndex, faceIndex + 1, faceIndex + 2));
+			} else if (face.faceLength == 4) {
+				obj.faces().add(
+						new Face(faceIndex, faceIndex + 1, faceIndex + 3));
+				obj.faces().add(
+						new Face(faceIndex + 1, faceIndex + 2, faceIndex + 3));
+			}
+
+			faceIndex += face.faceLength;
+		}
+
+		if (hasBitmaps) {
+			try{
+				obj.textures().addById(textureAtlas.getId());
+			}catch(Exception e)
+			{
+				e.printStackTrace();
+			}
+		}
+
+		cleanup();
+	}
+
+	
+	public void calculateFaceNormal(ParseObjectFace face)
+	{
+		Number3d v1 = vertices.get(face.v[0]);
+		Number3d v2 = vertices.get(face.v[1]);
+		Number3d v3 = vertices.get(face.v[2]);
+		
+		Number3d vector1 = Number3d.subtract(v2, v1);
+		Number3d vector2 = Number3d.subtract(v3, v1);
+		
+		Number3d normal = new Number3d();
+		normal.x = (vector1.y * vector2.z) - (vector1.z * vector2.y);
+		normal.y = -((vector2.z * vector1.x) - (vector2.x * vector1.z));
+		normal.z = (vector1.x * vector2.y) - (vector1.y * vector2.x);
+		
+		double normFactor = Math.sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z));
+		
+		normal.x /= normFactor;
+		normal.y /= normFactor;
+		normal.z /= normFactor;
+		
+        normals.add(normal);
+        
+        int index = normals.size() - 1;
+        face.n = new int[3];
+        face.n[0] = index;
+        face.n[1] = index;
+        face.n[2] = index;
+        face.hasn = true;
+	}
+
+	
+	protected void cleanup() {
+		faces.clear();
+	}
+}

+ 12 - 0
app/src/main/java/min3d/parser/ParseObjectFace.java

@@ -0,0 +1,12 @@
+package min3d.parser;
+
+public class ParseObjectFace {
+	public int[] v;
+	public int[] uv;
+	public int[] n;
+	public int faceLength;
+	public boolean hasuv;
+	public boolean hasn;
+	public String materialKey;
+
+}

+ 40 - 0
app/src/main/java/min3d/parser/Parser.java

@@ -0,0 +1,40 @@
+package min3d.parser;
+
+import android.content.res.Resources;
+
+/**
+ * Parser factory class. Specify the parser type and the corresponding
+ * specialized class will be returned.
+ * @author dennis.ippel
+ *
+ */
+public class Parser {
+	/**
+	 * Parser types enum
+	 * @author dennis.ippel
+	 *
+	 */
+	public static enum Type { OBJ, MAX_3DS, MD2 };
+	
+	/**
+	 * Create a parser of the specified type.
+	 * @param type
+	 * @param resources
+	 * @param resourceID
+	 * @return
+	 */
+	public static IParser createParser(Type type, Resources resources, String resourceID, boolean generateMipMap)
+	{
+		switch(type)
+		{
+			case OBJ:
+				return new ObjParser(resources, resourceID, generateMipMap);
+			case MAX_3DS:
+				return new Max3DSParser(resources, resourceID, generateMipMap);
+			case MD2:
+				return new MD2Parser(resources, resourceID, generateMipMap);
+		}
+		
+		return null;
+	}
+}

+ 32 - 0
app/src/main/java/min3d/vos/AbstractDirtyManaged.java

@@ -0,0 +1,32 @@
+package min3d.vos;
+
+import min3d.interfaces.IDirtyManaged;
+import min3d.interfaces.IDirtyParent;
+
+
+public abstract class AbstractDirtyManaged implements IDirtyManaged
+{
+	protected IDirtyParent _parent;
+	protected boolean _dirty;
+	
+	public AbstractDirtyManaged(IDirtyParent $parent)
+	{
+		_parent = $parent;
+	}
+	
+	public boolean isDirty()
+	{
+		return _dirty;
+	}
+	
+	public void setDirtyFlag()
+	{
+		_dirty = true;
+		if (_parent != null) _parent.onDirty();
+	}
+	
+	public void clearDirtyFlag()
+	{
+		_dirty = false;
+	}
+}

+ 24 - 0
app/src/main/java/min3d/vos/BooleanManaged.java

@@ -0,0 +1,24 @@
+package min3d.vos;
+
+import min3d.interfaces.IDirtyParent;
+
+public class BooleanManaged extends AbstractDirtyManaged 
+{
+	private boolean _b;
+
+	public BooleanManaged(boolean $value, IDirtyParent $parent)
+	{
+		super($parent);
+		set($value);
+	}
+	
+	public boolean get()
+	{
+		return _b;
+	}
+	public void set(boolean $b)
+	{
+		_b = $b;
+		setDirtyFlag();
+	}
+}

+ 19 - 0
app/src/main/java/min3d/vos/CameraVo.java

@@ -0,0 +1,19 @@
+package min3d.vos;
+
+/**
+ * Encapsulates camera-related properties, including view frustrum.
+ */
+public class CameraVo
+{
+	public Number3d position = new Number3d(0,0, 15); // ... note, not 'managed'
+	public Number3d target = new Number3d(0,0,0);
+	public Number3d upAxis = new Number3d(0,1,0);
+	
+	public FrustumManaged frustum = new FrustumManaged(null);
+
+	
+	public CameraVo()
+	{
+	}
+
+}

+ 100 - 0
app/src/main/java/min3d/vos/Color4.java

@@ -0,0 +1,100 @@
+package min3d.vos;
+
+import java.nio.FloatBuffer;
+
+import min3d.Utils;
+
+/**
+ * Simple struct/VO.
+ * Expected range of [0,255] for r, g, b, and a
+ * (Unfortunately stored as shorts's, not byte's, since Java bytes only go up to 128 :( )
+ *  
+ */
+public class Color4 
+{
+	public short r;
+	public short g;
+	public short b;
+	public short a;
+	
+	
+	public Color4()
+	{
+		r = (short)255;
+		g = (short)255;
+		b = (short)255;
+		a = (short)255;
+	}
+	
+	public Color4(short $r, short $g, short $b, short $a)
+	{
+		r = $r;
+		g = $g;
+		b = $b;
+		a = $a;
+	}
+
+	/**
+	 * Convenience method which casts the int arguments to short for you. 
+	 */
+	public Color4(int $r, int $g, int $b, int $a)
+	{
+		r = (short)$r;
+		g = (short)$g;
+		b = (short)$b;
+		a = (short)$a;
+	}
+
+	/**
+	 * Convenience method which casts the float arguments to short for you. 
+	 */
+	public Color4(float $r, float $g, float $b, float $a)
+	{
+		r = (short)$r;
+		g = (short)$g;
+		b = (short)$b;
+		a = (short)$a;
+	}
+
+	/**
+	 *  Convenience method to set all properties in one line.
+	 */
+	public void setAll(short $r, short $g, short $b, short $a)
+	{
+		r = $r;
+		g = $g;
+		b = $b;
+		a = $a;
+	}
+	
+	/**
+	 * Convenience method to set all properties off one 32-bit argb value 
+	 */
+	public void setAll(long $argb32)
+	{
+		a = (short) (($argb32 >> 24) & 0x000000FF);
+		r = (short) (($argb32 >> 16) & 0x000000FF);
+		g = (short) (($argb32 >> 8) & 0x000000FF);
+		b = (short) (($argb32) & 0x000000FF);		
+	}
+	
+	@Override
+	public String toString()
+	{
+		return "r:" + r + ", g:" + g + ", b:" + b + ", a:" + a;
+	}
+	
+	public FloatBuffer toFloatBuffer()
+	{
+		return Utils.makeFloatBuffer4(r,g,b,a);
+	}
+	
+	public void toFloatBuffer(FloatBuffer $floatBuffer)
+	{
+		$floatBuffer.position(0);
+		$floatBuffer.put((float)r / 255f);
+		$floatBuffer.put((float)g / 255f);
+		$floatBuffer.put((float)b / 255f);
+		$floatBuffer.put((float)a / 255f);
+	}
+}

+ 202 - 0
app/src/main/java/min3d/vos/Color4Managed.java

@@ -0,0 +1,202 @@
+package min3d.vos;
+
+import java.nio.FloatBuffer;
+
+import min3d.Utils;
+import min3d.interfaces.IDirtyParent;
+
+/**
+ * Same functionality as Color4, but uses proper accessors to r,g,b, and a properties, 
+ * rather than VO-style public variables, so that 'dirty flag' can be managed properly.
+ * 
+ * It is also backed by a FloatBuffer.
+ */
+public class Color4Managed extends AbstractDirtyManaged
+{
+	private short _r;
+	private short _g;
+	private short _b;
+	private short _a;
+	
+	private FloatBuffer _fb;
+	
+	
+	public Color4Managed(IDirtyParent $parent)
+	{
+		super($parent);
+		
+		_r = (short)255;
+		_g = (short)255;
+		_b = (short)255;
+		_a = (short)255;
+
+		_fb = this.toFloatBuffer();
+		
+		setDirtyFlag();
+		
+	}
+	
+	public Color4Managed(short $r, short $g, short $b, short $a, IDirtyParent $parent)
+	{
+		super($parent);
+		
+		_r = $r;
+		_g = $g;
+		_b = $b;
+		_a = $a;
+
+		_fb = this.toFloatBuffer();
+
+		setDirtyFlag();
+	}
+
+	/**
+	 * Convenience method which casts the int arguments to short for you. 
+	 */
+	public Color4Managed(int $r, int $g, int $b, int $a, IDirtyParent $parent)
+	{
+		super($parent);
+
+		_r = (short)$r;
+		_g = (short)$g;
+		_b = (short)$b;
+		_a = (short)$a;
+
+		_fb = this.toFloatBuffer();
+
+		setDirtyFlag();
+	}
+
+	/**
+	 *  Convenience method to set all properties in one line.
+	 */
+	public void setAll(short $r, short $g, short $b, short $a)
+	{
+		_r = $r;
+		_g = $g;
+		_b = $b;
+		_a = $a;
+
+		setDirtyFlag();
+	}
+	
+	public void setAll(int $r, int $g, int $b, int $a)
+	{
+		setAll((short)$r, (short)$g, (short)$b, (short)$a);
+	}
+	
+	public Color4 toColor4()
+	{
+		return new Color4(_r,_g,_b,_a);
+	}
+	
+	/**
+	 * Convenience method to set all properties off one 32-bit rgba value 
+	 */
+	public void setAll(long $argb32)
+	{
+		_a = (short) (($argb32 >> 24) & 0x000000FF);
+		_r = (short) (($argb32 >> 16) & 0x000000FF);
+		_g = (short) (($argb32 >> 8) & 0x000000FF);
+		_b = (short) (($argb32) & 0x000000FF);		
+		
+		setDirtyFlag();
+	}
+	
+	public void setAll(Color4 $color)
+	{
+		setAll($color.r, $color.g, $color.b, $color.a);
+	}
+
+	public short r()
+	{
+		return _r;
+	}
+	public void r(short $r)
+	{
+		_r = $r;
+		setDirtyFlag();
+	}
+	
+	public short g()
+	{
+		return _g;
+	}
+	public void g(short $g)
+	{
+		_g = $g;
+		setDirtyFlag();
+	}
+	
+	public short b()
+	{
+		return _b;
+	}
+	public void b(short $b)
+	{
+		_b = $b;
+		setDirtyFlag();
+	}
+	
+	public short a()
+	{
+		return _a;
+	}
+	public void a(short $a)
+	{
+		_a = $a;
+		setDirtyFlag();
+	}
+
+	/**
+	 * Convenience method
+	 */
+	public FloatBuffer toFloatBuffer()
+	{
+		return Utils.makeFloatBuffer4(
+			(float)r() / 255f,
+			(float)g() / 255f,
+			(float)b() / 255f,
+			(float)a() / 255f
+		);
+	}
+
+	/**
+	 * Convenience method
+	 */
+	public void toFloatBuffer(FloatBuffer $floatBuffer)
+	{
+		$floatBuffer.position(0);
+		$floatBuffer.put((float)r() / 255f);
+		$floatBuffer.put((float)g() / 255f);
+		$floatBuffer.put((float)b() / 255f);
+		$floatBuffer.put((float)a() / 255f);
+		$floatBuffer.position(0);
+	}
+	
+	//
+	
+	/**
+	 * Used by Renderer
+	 */
+	public FloatBuffer floatBuffer()
+	{
+		return _fb;
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	public void commitToFloatBuffer()
+	{
+		this.toFloatBuffer(_fb);
+	}
+
+	@Override
+	public String toString()
+	{
+		return "r:" + _r + ", g:" + _g + ", b:" + _b + ", a:" + _a;
+	}
+
+	
+}

+ 29 - 0
app/src/main/java/min3d/vos/Face.java

@@ -0,0 +1,29 @@
+package min3d.vos;
+
+/**
+ * Simple VO with three properties representing vertex indicies. 
+ * Is not necessary for functioning of engine, just a convenience.
+ */
+public class Face 
+{
+	public short a;
+	public short b;
+	public short c;
+	
+	public Face(short $a, short $b, short $c)
+	{
+		a = $a;
+		b = $b;
+		c = $c;
+	}
+	
+	/**
+	 * Convenience method to cast int arguments to short's 
+	 */
+	public Face(int $a, int $b, int $c)
+	{
+		a = (short) $a;
+		b = (short) $b;
+		c = (short) $c;
+	}
+}

+ 24 - 0
app/src/main/java/min3d/vos/FloatManaged.java

@@ -0,0 +1,24 @@
+package min3d.vos;
+
+import min3d.interfaces.IDirtyParent;
+
+public class FloatManaged extends AbstractDirtyManaged 
+{
+	private float _f;
+
+	public FloatManaged(float $value, IDirtyParent $parent)
+	{
+		super($parent);
+		set($value);
+	}
+	
+	public float get()
+	{
+		return _f;
+	}
+	public void set(float $f)
+	{
+		_f = $f;
+		setDirtyFlag();
+	}
+}

+ 21 - 0
app/src/main/java/min3d/vos/FogType.java

@@ -0,0 +1,21 @@
+package min3d.vos;
+
+import javax.microedition.khronos.opengles.GL10;
+
+public enum FogType {
+	LINEAR (GL10.GL_LINEAR),
+	EXP (GL10.GL_EXP),
+	EXP2 (GL10.GL_EXP2);
+	
+	private final int _glValue;
+	
+	private FogType(int $glValue)
+	{
+		_glValue = $glValue;
+	}
+	
+	public int glValue()
+	{
+		return _glValue;
+	}
+}

+ 99 - 0
app/src/main/java/min3d/vos/FrustumManaged.java

@@ -0,0 +1,99 @@
+package min3d.vos;
+
+import min3d.interfaces.IDirtyManaged;
+import min3d.interfaces.IDirtyParent;
+
+/**
+ * 'Managed' VO for the view frustrum. Used by Camera.
+ */
+public class FrustumManaged extends AbstractDirtyManaged 
+{
+	private float _shortSideLength;
+	private float _horizontalCenter;
+	private float _verticalCenter;
+	private float _zNear;
+	private float _zFar;
+	
+	
+	public FrustumManaged(IDirtyParent $parent)
+	{
+		super($parent);
+		
+		_horizontalCenter = 0f;
+		_verticalCenter = 0f;
+		_shortSideLength = 1.0f;
+		
+		_zNear = 1.0f;
+		_zFar = 100.0f;
+	}
+
+	public FrustumManaged(float $horizontalCenter, float $verticalCenter, float $shortSideLength, float $zNear, float $zFar, IDirtyParent $parent)
+	{
+		super($parent);
+		
+		_horizontalCenter = $horizontalCenter;
+		_verticalCenter = $verticalCenter;
+		_shortSideLength = $shortSideLength;
+		
+		_zNear = $zNear;
+		_zFar = $zFar;
+	}
+	
+	/**
+	 * Defines the length of the shorter side of the horizontal and vertical dimensions. 
+	 * (The longer side will be automatically adjusted to preserve pixel aspect ratio)
+	 */
+	public float shortSideLength() {
+		return _shortSideLength;
+	}
+
+	public void shortSideLength(float shortSideLength) {
+		_shortSideLength = shortSideLength;
+		setDirtyFlag();
+	}
+
+	public float horizontalCenter() {
+		return _horizontalCenter;
+	}
+
+	public void horizontalCenter(float horizontalCenter) {
+		_horizontalCenter = horizontalCenter;
+		setDirtyFlag();
+	}
+
+	public float verticalCenter() {
+		return _verticalCenter;
+	}
+
+	public void verticalCenter(float verticalCenter) {
+		_verticalCenter = verticalCenter;
+		setDirtyFlag();
+	}
+
+	/**
+	 * Corresponds to OpenGL glFrustumf param
+	 */
+	public float zNear() {
+		return _zNear;
+	}
+
+	public void zNear(float zNear) {
+		_zNear = zNear;
+		setDirtyFlag();
+	}
+
+	/**
+	 * Corresponds to OpenGL glFrustumf param
+	 */
+	public float zFar() {
+		return _zFar;
+	}
+
+	public void zFar(float zFar) {
+		_zFar = zFar;
+		setDirtyFlag();
+	}
+	
+	//
+	
+}

+ 228 - 0
app/src/main/java/min3d/vos/Light.java

@@ -0,0 +1,228 @@
+package min3d.vos;
+
+import java.nio.FloatBuffer;
+
+import min3d.Utils;
+import min3d.interfaces.IDirtyParent;
+
+/**
+ * Light must be added to Scene to take effect.
+ * 
+ * Eg, "scene.lights().add(myLight);"  
+ */
+public class Light extends AbstractDirtyManaged implements IDirtyParent
+{
+	/**
+	 * Position is relative to eye space, not world space.
+	 */
+	public Number3dManaged 		position;
+	
+	/**
+	 * Direction is a vector and should be normalized.
+	 */
+	public Number3dManaged 		direction;  
+	
+	public Color4Managed 		ambient;
+	public Color4Managed 		diffuse;
+	public Color4Managed 		specular;
+	public Color4Managed 		emissive;
+	
+	private LightType			_type;
+
+	public Light()
+	{
+		super(null);
+		
+		 ambient = new Color4Managed(128,128,128, 255, this);
+		 diffuse = new Color4Managed(255,255,255, 255, this);
+		 specular = new Color4Managed(0,0,0,255, this);
+		 emissive = new Color4Managed(0,0,0,255, this);
+		 
+		 position = new Number3dManaged(0f, 0f, 1f, this); 			
+		 
+		 direction = new Number3dManaged(0f, 0f, -1f, this);	
+		 _spotCutoffAngle = new FloatManaged(180, this);		
+		 _spotExponent = new FloatManaged(0f, this);			
+		 
+		 _attenuation = new Number3dManaged(1f,0f,0f, this); 	
+		 
+		 _type = LightType.DIRECTIONAL;								
+		 
+		 _isVisible = new BooleanManaged(true, this);
+		 
+		 _positionAndTypeBuffer = Utils.makeFloatBuffer4(0,0,0,0);
+		 
+		 setDirtyFlag();
+	}
+
+	public boolean isVisible()
+	{
+		return _isVisible.get();
+	}
+	public void isVisible(Boolean $b)
+	{
+		_isVisible.set($b);
+	}
+	
+	/**
+	 * Default is DIRECTIONAL, matching OpenGL's default value.
+	 */
+	public LightType type()
+	{
+		return _type;
+	}
+	
+	public void type(LightType $type)
+	{
+		_type = $type;
+		position.setDirtyFlag(); // .. position and type share same data structure
+	}
+	
+	/**
+	 * 0 = no attenuation towards edges of spotlight. Max is 128.
+	 * Default is 0, matching OpenGL's default value.
+	 */
+	public float spotExponent()
+	{
+		return _spotExponent.get();
+	}
+	public void spotExponent(float $f)
+	{
+		if ($f < 0) $f = 0;
+		if ($f > 128) $f = 128;
+		_spotExponent.set($f);
+	}
+	
+	/**
+	 * Legal range is 0 to 90, plus 180, which is treated by OpenGL to mean no cutoff.
+	 * Default is 180, matching OpenGL's default value.
+	 */
+	public float spotCutoffAngle()
+	{
+		return _spotCutoffAngle.get();
+	}
+	public void spotCutoffAngle(Float $f)
+	{
+		if ($f < 0) 
+			_spotCutoffAngle.set(0);
+		else if ($f <= 90)
+			_spotCutoffAngle.set($f);
+		else if ($f == 180)
+			_spotCutoffAngle.set($f);
+		else
+			_spotCutoffAngle.set(90);
+	}
+	
+	/**
+	 * No cutoff angle (ie, no spotlight effect)
+	 * (represented internally with a value of 180)
+	 */
+	public void spotCutoffAngleNone()
+	{
+		_spotCutoffAngle.set(180);
+	}
+	
+	public float attenuationConstant()
+	{
+		return _attenuation.getX();
+	}
+	public void attenuationConstant(float $normalizedValue)
+	{
+		_attenuation.setX($normalizedValue);
+		setDirtyFlag();
+	}
+	
+	public float attenuationLinear()
+	{
+		return _attenuation.getY();
+	}
+	public void attenuationLinear(float $normalizedValue)
+	{
+		_attenuation.setY($normalizedValue);
+		setDirtyFlag();
+	}
+	
+	public float attenuationQuadratic()
+	{
+		return _attenuation.getZ();
+	}
+	public void attenuationQuadratic(float $normalizedValue)
+	{
+		_attenuation.setZ($normalizedValue);
+		setDirtyFlag();
+	}
+	
+	/**
+	 * Defaults are 1,0,0 (resulting in no attenuation over distance), 
+	 * which match OpenGL default values. 
+	 */
+	public void attenuationSetAll(float $constant, float $linear, float $quadratic)
+	{
+		_attenuation.setAll($constant, $linear, $quadratic);
+		setDirtyFlag();
+	}
+
+	//
+	
+	public void setAllDirty()
+	{
+		position.setDirtyFlag();
+		ambient.setDirtyFlag();
+		diffuse.setDirtyFlag();
+		specular.setDirtyFlag();
+		emissive.setDirtyFlag();
+		direction.setDirtyFlag();
+		_spotCutoffAngle.setDirtyFlag();
+		_spotExponent.setDirtyFlag();
+		_attenuation.setDirtyFlag();
+		_isVisible.setDirtyFlag();
+	}
+
+	public void onDirty()
+	{
+		setDirtyFlag();
+	}
+	
+	/**
+	 * Used by Renderer
+	 * Normal clients of this class should use "isVisible" getter/setter.
+	 */
+	public BooleanManaged _isVisible;
+
+	/**
+	 * Used by Renderer
+	 */
+	public FloatBuffer _positionAndTypeBuffer;
+
+	/**
+	 * Used by Renderer
+	 */
+	public void commitPositionAndTypeBuffer()
+	{
+		// GL_POSITION takes 4 arguments, the first 3 being x/y/z position, 
+		// and the 4th being what we're calling 'type' (positional or directional)
+		
+		_positionAndTypeBuffer.position(0);
+		_positionAndTypeBuffer.put(position.getX());
+		_positionAndTypeBuffer.put(position.getY());
+		_positionAndTypeBuffer.put(position.getZ());
+		_positionAndTypeBuffer.put(_type.glValue());
+		_positionAndTypeBuffer.position(0);
+	}
+	
+	/**
+	 * Used by Renderer. 
+	 * Normal clients of this class should use "useSpotProperties" getter/setter.
+	 */
+	public FloatManaged _spotExponent; 
+
+	/**
+	 * Used by Renderer. Normal clients of this class should use "useSpotProperties" getter/setter.
+	 */
+	public FloatManaged _spotCutoffAngle;
+	
+	/**
+	 * Used by Renderer. Normal clients of this class should use attenuation getter/setters.
+	 */
+	public Number3dManaged _attenuation; // (the 3 properties of N3D used for the 3 attenuation properties)
+}

+ 20 - 0
app/src/main/java/min3d/vos/LightType.java

@@ -0,0 +1,20 @@
+package min3d.vos;
+
+public enum LightType 
+{
+	DIRECTIONAL (0f),
+	POSITIONAL (1f); // Any value other than 0 treated as non-directional
+	
+	
+	private final float _glValue;
+	
+	private LightType(float $glValue)
+	{
+		_glValue = $glValue;
+	}
+	
+	public float glValue()
+	{
+		return _glValue;
+	}
+}

+ 165 - 0
app/src/main/java/min3d/vos/Number3d.java

@@ -0,0 +1,165 @@
+package min3d.vos;
+
+import java.io.Serializable;
+
+/**
+ * Simple VO holding x,y, and z values. Plus helper math functions.
+ * Care should be taken to avoid creating Number3d instances unnecessarily. 
+ * Its use is not required for the construction of vertices.
+ */
+public class Number3d implements Serializable
+{
+	public float x;
+	public float y;
+	public float z;
+	
+	private transient static Number3d _temp = new Number3d();
+
+
+	
+	public Number3d()
+	{
+		x = 0;
+		y = 0;
+		z = 0;
+	}
+	
+	public Number3d(float $x, float $y, float $z)
+	{
+		x = $x;
+		y = $y;
+		z = $z;
+	}
+	
+	//
+	
+	public void setAll(float $x, float $y, float $z)
+	{
+		x = $x;
+		y = $y;
+		z = $z;
+	}
+	
+	public void setAllFrom(Number3d $n)
+	{
+		x = $n.x;
+		y = $n.y;
+		z = $n.z;
+	}
+	
+	public void normalize()
+	{
+		float mod = (float) Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z );
+
+		if( mod != 0 && mod != 1)
+		{
+			mod = 1 / mod; 
+			this.x *= mod;
+			this.y *= mod;
+			this.z *= mod;
+		}
+	}
+
+	public void add(Number3d n)
+	{
+		this.x += n.x;
+		this.y += n.y;
+		this.z += n.z;
+	}
+	
+	public void subtract(Number3d n)
+	{
+		this.x -= n.x;
+		this.y -= n.y;
+		this.z -= n.z;
+	}
+	
+	public void multiply(Float f)
+	{
+		this.x *= f;
+		this.y *= f;
+		this.z *= f;
+	}
+	
+	public float length()
+	{
+		return (float) Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z );
+	}
+	
+	public Number3d clone()
+	{
+		return new Number3d(x,y,z);
+	}
+	
+	public void rotateX(float angle)
+	{
+		float cosRY = (float) Math.cos(angle);
+		float sinRY = (float) Math.sin(angle);
+
+		_temp.setAll(this.x, this.y, this.z); 
+
+		this.y = (_temp.y*cosRY)-(_temp.z*sinRY);
+		this.z = (_temp.y*sinRY)+(_temp.z*cosRY);
+	}
+	
+	public void rotateY(float angle)
+	{
+		float cosRY = (float) Math.cos(angle);
+		float sinRY = (float) Math.sin(angle);
+
+		_temp.setAll(this.x, this.y, this.z); 
+		
+		this.x = (_temp.x*cosRY)+(_temp.z*sinRY);
+		this.z = (_temp.x*-sinRY)+(_temp.z*cosRY);
+	}
+	
+	public void rotateZ(float angle)
+	{
+		float cosRY = (float) Math.cos(angle);
+		float sinRY = (float) Math.sin(angle);
+
+		_temp.setAll(this.x, this.y, this.z); 		
+
+		this.x = (_temp.x*cosRY)-(_temp.y*sinRY);
+		this.y = (_temp.x*sinRY)+(_temp.y*cosRY);
+	}
+	
+	
+	@Override
+	public String toString()
+	{
+		return "("+x + ", " + y + ", " + z+")";
+	}
+	
+	//
+
+	public static Number3d add(Number3d a, Number3d b)
+	{
+		return new Number3d(a.x + b.x, a.y + b.y, a.z + b.z);
+	}
+
+	public static Number3d subtract(Number3d a, Number3d b)
+	{
+		return new Number3d(a.x - b.x, a.y - b.y, a.z - b.z);
+	}
+	
+	public static Number3d multiply(Number3d a, Number3d b)
+	{
+		return new Number3d(a.x * b.x, a.y * b.y, a.z * b.z);
+	}
+	
+	public static Number3d cross(Number3d v, Number3d w)
+	{
+		return new Number3d((w.y * v.z) - (w.z * v.y), (w.z * v.x) - (w.x * v.z), (w.x * v.y) - (w.y * v.x));
+	}
+	
+	public static float dot(Number3d v, Number3d w)
+	{
+		return ( v.x * w.x + v.y * w.y + w.z * v.z );
+	}
+	
+	// * 	Math functions thanks to Papervision3D AS3 library
+	// 		http://code.google.com/p/papervision3d/
+
+
+}

+ 139 - 0
app/src/main/java/min3d/vos/Number3dManaged.java

@@ -0,0 +1,139 @@
+package min3d.vos;
+
+import java.nio.FloatBuffer;
+
+import android.util.Log;
+
+import min3d.Min3d;
+import min3d.Utils;
+import min3d.interfaces.IDirtyParent;
+
+/**
+ * 'Managed' version of Number3d VO 
+ */
+public class Number3dManaged extends AbstractDirtyManaged 
+{
+	private float _x;
+	private float _y;
+	private float _z;
+	
+	private FloatBuffer _fb;
+	
+	public Number3dManaged(IDirtyParent $parent)
+	{
+		super($parent);
+		_x = 0;
+		_y = 0;
+		_z = 0;
+		_fb = this.toFloatBuffer();
+		setDirtyFlag();
+	}
+	
+	public Number3dManaged(float $x, float $y, float $z, IDirtyParent $parent)
+	{
+		super($parent);
+		_x = $x;
+		_y = $y;
+		_z = $z;
+		_fb = this.toFloatBuffer();
+		setDirtyFlag();
+	}
+	
+	public float getX() {
+		return _x;
+	}
+
+	public void setX(float x) {
+		_x = x;
+		setDirtyFlag();
+	}
+
+	public float getY() {
+		return _y;
+	}
+
+	public void setY(float y) {
+		_y = y;
+		setDirtyFlag();
+	}
+
+	public float getZ() {
+		return _z;
+	}
+
+	public void setZ(float z) {
+		_z = z;
+		setDirtyFlag();
+	}
+
+	public void setAll(float $x, float $y, float $z)
+	{
+		_x = $x;
+		_y = $y;
+		_z = $z;
+		setDirtyFlag();
+	}
+	
+	public void setAllFrom(Number3d $n)
+	{
+		_x = $n.x;
+		_y = $n.y;
+		_z = $n.z;
+		setDirtyFlag();
+	}
+
+	public void setAllFrom(Number3dManaged $n)
+	{
+		_x = $n.getX();
+		_y = $n.getY();
+		_z = $n.getZ();
+		setDirtyFlag();
+	}
+
+	public Number3d toNumber3d()
+	{
+		return new Number3d(_x,_y,_z);
+	}
+	
+	@Override
+	public String toString()
+	{
+		return _x + "," + _y + "," + _z; 
+	}
+	
+	/**
+	 * Convenience method
+	 */
+	public FloatBuffer toFloatBuffer()
+	{
+		return Utils.makeFloatBuffer3(_x, _y, _z);
+	}
+	
+	/**
+	 * Convenience method
+	 */
+	public void toFloatBuffer(FloatBuffer $floatBuffer)
+	{
+		$floatBuffer.position(0);
+		$floatBuffer.put(_x);
+		$floatBuffer.put(_y);
+		$floatBuffer.put(_z);
+		$floatBuffer.position(0);
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	public FloatBuffer floatBuffer()
+	{
+		return _fb;
+	}
+
+	/**
+	 * Used by Renderer
+	 */
+	public void commitToFloatBuffer()
+	{
+		toFloatBuffer(_fb);
+	}
+}

+ 26 - 0
app/src/main/java/min3d/vos/RenderType.java

@@ -0,0 +1,26 @@
+package min3d.vos;
+
+import javax.microedition.khronos.opengles.GL10;
+
+public enum RenderType 
+{
+	POINTS (GL10.GL_POINTS),
+	LINES (GL10.GL_LINES),
+	LINE_LOOP (GL10.GL_LINE_LOOP),
+	LINE_STRIP (GL10.GL_LINE_STRIP),
+	TRIANGLES (GL10.GL_TRIANGLES),
+	TRIANGLE_STRIP (GL10.GL_TRIANGLE_STRIP),
+	TRIANGLE_FAN (GL10.GL_TRIANGLE_FAN);
+	
+	private final int _glValue;
+	
+	private RenderType(int $glValue)
+	{
+		_glValue = $glValue;
+	}
+	
+	public int glValue()
+	{
+		return _glValue;
+	}
+}

+ 23 - 0
app/src/main/java/min3d/vos/ShadeModel.java

@@ -0,0 +1,23 @@
+package min3d.vos;
+
+import javax.microedition.khronos.opengles.GL10;
+
+public enum ShadeModel 
+{
+	SMOOTH (GL10.GL_SMOOTH),
+	FLAT (GL10.GL_FLAT);
+
+	
+	private final int _glConstant;
+	
+	private ShadeModel(int $glConstant)
+	{
+		_glConstant = $glConstant;
+	}
+	
+	public int glConstant()
+	{
+		return _glConstant;
+	}
+}
+

+ 25 - 0
app/src/main/java/min3d/vos/ShadeModelManaged.java

@@ -0,0 +1,25 @@
+package min3d.vos;
+
+import min3d.interfaces.IDirtyParent;
+
+
+public class ShadeModelManaged extends AbstractDirtyManaged 
+{
+	private ShadeModel _shadeModel;
+
+	public ShadeModelManaged(IDirtyParent $parent)
+	{
+		super($parent);
+		set(ShadeModel.SMOOTH);
+	}
+	
+	public ShadeModel get()
+	{
+		return _shadeModel;
+	}
+	public void set(ShadeModel $shadeModel)
+	{
+		_shadeModel = $shadeModel;
+		_dirty = true; // no need for callback
+	}
+}

+ 34 - 0
app/src/main/java/min3d/vos/TexEnvxVo.java

@@ -0,0 +1,34 @@
+package min3d.vos;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Structure to hold one OpenGL texture environment command.
+ * 
+ * Is the equivalent of calling:
+ * 		glTexEnvx(GL10.GL_TEXTURE_ENV, pname, param);
+ */
+public class TexEnvxVo 
+{
+	public int pname = GL10.GL_TEXTURE_ENV_MODE;
+	public int param = GL10.GL_MODULATE;
+	
+	public TexEnvxVo()
+	{
+	}
+	
+	public TexEnvxVo(int $pname, int $param)
+	{
+		pname = $pname;
+		param = $param;
+	}
+
+	/**
+	 * Convenience method
+	 */
+	public void setAll(int $pname, int $param)
+	{
+		pname = $pname;
+		param = $param;
+	}
+}

+ 61 - 0
app/src/main/java/min3d/vos/TextureVo.java

@@ -0,0 +1,61 @@
+package min3d.vos;
+
+import java.util.ArrayList;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Contains the properties of a texture which can be assigned to an object.
+ * An object can be assigned multiple TextureVo's by adding them to 
+ * the Object3d's TextureList (usually up to just 2 w/ current Android hardware).
+ * 
+ *  The "textureEnvs" ArrayList defines what texture environment commands 
+ *  will be sent to OpenGL for the texture. Typically, this needs to hold
+ *  just one TextureEnvVo, but can hold an arbitrary number, for more
+ *  complex 'layering' operations. 
+ *  
+ *  TODO: Allow for adding glTexEnvf commands (float instead of int)
+ *  
+ *  TODO: Ability to assign arbitrary UV lists per-TextureVo? (Non-trivial...)
+ */
+public class TextureVo 
+{
+	/**
+	 * The texureId in the TextureManager that corresponds to an uploaded Bitmap
+	 */
+	public String textureId;
+	
+	/**
+	 * Determines if U and V ("S" and "T" in OpenGL parlance) repeat, or are 'clamped'
+	 * (Defaults to true, matching OpenGL's default setting)
+	 */
+	public boolean repeatU = true;
+	public boolean repeatV = true;
+
+	/**
+	 * The U/V offsets for the texture (rem, normal range of U and V are 0 to 1)
+	 */
+	public float offsetU = 0;
+	public float offsetV = 0;
+	
+	/**
+	 * A list of TexEnvVo's that define how texture is composited in the output.
+	 * Normally contains just one element.
+	 */
+	public ArrayList<TexEnvxVo> textureEnvs;
+
+	//
+	
+	public TextureVo(String $textureId, ArrayList<TexEnvxVo> $textureEnvVo)
+	{
+		textureId = $textureId;
+		textureEnvs = $textureEnvVo;
+	}
+	
+	public TextureVo(String $textureId)
+	{
+		textureId = $textureId;
+		textureEnvs = new ArrayList<TexEnvxVo>();
+		textureEnvs.add( new TexEnvxVo());
+	}
+}

+ 28 - 0
app/src/main/java/min3d/vos/Uv.java

@@ -0,0 +1,28 @@
+package min3d.vos;
+
+/**
+ * Simple VO used for texture positioning 
+ */
+public class Uv 
+{
+	public float u;
+	public float v;
+	
+	public Uv()
+	{
+		u = 0;
+		v = 0;
+	}
+	
+	public Uv(float $u, float $v)
+	{
+		u = $u;
+		v = $v;
+	}
+	
+	public Uv clone()
+	{
+		return new Uv(u, v);
+	}
+	// Rem, v == 0 @ 'bottom', v == 1 @ 'top'
+}

+ 19 - 0
app/src/main/java/min3d/vos/Vertex3d.java

@@ -0,0 +1,19 @@
+package min3d.vos;
+
+
+/**
+ * Container holding VO's of vertex-related information.
+ * Not required for operation of framework, but may be helpful as a convenience. 
+ */
+public class Vertex3d 
+{
+	public Number3d		position = new Number3d();
+	public Uv 			uv;								
+	public Number3d		normal;							
+	public Color4 		color;					
+	
+	
+	public Vertex3d()
+	{
+	}
+}

BIN
app/src/main/res/drawable/barong.jpg


BIN
app/src/main/res/drawable/camaro.jpg


BIN
app/src/main/res/drawable/ceiling.jpg


BIN
app/src/main/res/drawable/checkerboard.png


BIN
app/src/main/res/drawable/clouds_alpha2b.png


BIN
app/src/main/res/drawable/deadmickey.jpg


BIN
app/src/main/res/drawable/earth.jpg


BIN
app/src/main/res/drawable/face_eyel_hi.jpg


BIN
app/src/main/res/drawable/face_eyer_hi.jpg


BIN
app/src/main/res/drawable/face_skin_hi.jpg


BIN
app/src/main/res/drawable/face_sock.jpg


BIN
app/src/main/res/drawable/floor.jpg


BIN
app/src/main/res/drawable/icon.png


BIN
app/src/main/res/drawable/jupiter.jpg


BIN
app/src/main/res/drawable/maqjpg.jpg


BIN
app/src/main/res/drawable/monster.jpg


BIN
app/src/main/res/drawable/moon.jpg


BIN
app/src/main/res/drawable/ogrobase.jpg


BIN
app/src/main/res/drawable/revenant.jpg


BIN
app/src/main/res/drawable/stonetexture.jpg


BIN
app/src/main/res/drawable/uglysquares.png


BIN
app/src/main/res/drawable/white_with_alpha_hole.png


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác