Browse Source

Froms MystereARecouvrance

François Gautrais 7 years ago
commit
14810fa3b1
100 changed files with 13288 additions and 0 deletions
  1. 76 0
      app/src/main/AndroidManifest.xml
  2. 606 0
      app/src/main/java/app/mar/activities/ARActivity.java
  3. 55 0
      app/src/main/java/app/mar/activities/CreditsActivity.java
  4. 16 0
      app/src/main/java/app/mar/activities/EndActivity.java
  5. 7 0
      app/src/main/java/app/mar/activities/IViewerActivity.java
  6. 124 0
      app/src/main/java/app/mar/activities/ImageViewerActivity.java
  7. 37 0
      app/src/main/java/app/mar/activities/InfoActivity.java
  8. 62 0
      app/src/main/java/app/mar/activities/MARMenuActivity.java
  9. 231 0
      app/src/main/java/app/mar/activities/MediaViewerAcitvity.java
  10. 241 0
      app/src/main/java/app/mar/activities/MenuActivity.java
  11. 165 0
      app/src/main/java/app/mar/activities/ModelViewerActivity.java
  12. 107 0
      app/src/main/java/app/mar/activities/PermissionActivity.java
  13. 163 0
      app/src/main/java/app/mar/activities/PuzzleActivity.java
  14. 93 0
      app/src/main/java/app/mar/activities/ResourceListActivity.java
  15. 90 0
      app/src/main/java/app/mar/activities/SEMenuActivity.java
  16. 249 0
      app/src/main/java/app/mar/activities/SettingsActivity.java
  17. 81 0
      app/src/main/java/app/mar/activities/TransferActivity.java
  18. 85 0
      app/src/main/java/app/mar/ui/CameraPreview.java
  19. 31 0
      app/src/main/java/app/mar/ui/CustomListView.java
  20. 103 0
      app/src/main/java/app/mar/ui/CustomToggleButton.java
  21. 156 0
      app/src/main/java/app/mar/ui/MediaView.java
  22. 12 0
      app/src/main/java/app/mar/ui/OnToggleListener.java
  23. 169 0
      app/src/main/java/app/mar/ui/ResourceArrayAdapter.java
  24. 43 0
      app/src/main/java/app/mar/ui/RotateButton.java
  25. 77 0
      app/src/main/java/app/mar/ui/SelectButton.java
  26. 85 0
      app/src/main/java/app/mar/utils/AndroidResources.java
  27. 97 0
      app/src/main/java/app/mar/utils/FontChangeCrawler.java
  28. 78 0
      app/src/main/java/app/mar/utils/GpsStub.java
  29. 45 0
      app/src/main/java/app/mar/utils/JSONLoader.java
  30. 249 0
      app/src/main/java/app/mar/utils/ResourceManager.java
  31. 227 0
      app/src/main/java/app/mar/utils/SensorsManager.java
  32. 86 0
      app/src/main/java/app/mar/utils/Settings.java
  33. 154 0
      app/src/main/java/app/mar/utils/SlideBuffer.java
  34. 114 0
      app/src/main/java/app/mar/utils/files/FileManager.java
  35. 47 0
      app/src/main/java/app/mar/utils/files/JSONAssetsManager.java
  36. 157 0
      app/src/main/java/app/mar/utils/game/Area.java
  37. 645 0
      app/src/main/java/app/mar/utils/game/Game.java
  38. 42 0
      app/src/main/java/app/mar/utils/game/LocatedResources.java
  39. 131 0
      app/src/main/java/app/mar/utils/game/Place.java
  40. 148 0
      app/src/main/java/app/mar/utils/game/Player.java
  41. 188 0
      app/src/main/java/app/mar/utils/game/Resource.java
  42. 82 0
      app/src/main/java/app/mar/utils/game/Stage.java
  43. 134 0
      app/src/main/java/app/mar/utils/geometry/GPSPoint.java
  44. 98 0
      app/src/main/java/app/mar/utils/geometry/Point.java
  45. 118 0
      app/src/main/java/app/mar/utils/geometry/Shape.java
  46. 17 0
      app/src/main/java/min3d/Min3d.java
  47. 46 0
      app/src/main/java/min3d/Shared.java
  48. 84 0
      app/src/main/java/min3d/Utils.java
  49. 187 0
      app/src/main/java/min3d/animation/AnimationObject3d.java
  50. 103 0
      app/src/main/java/min3d/animation/KeyFrame.java
  51. 160 0
      app/src/main/java/min3d/core/Color4BufferList.java
  52. 190 0
      app/src/main/java/min3d/core/FacesBufferedList.java
  53. 149 0
      app/src/main/java/min3d/core/ManagedLightList.java
  54. 157 0
      app/src/main/java/min3d/core/Number3dBufferList.java
  55. 491 0
      app/src/main/java/min3d/core/Object3d.java
  56. 137 0
      app/src/main/java/min3d/core/Object3dContainer.java
  57. 153 0
      app/src/main/java/min3d/core/RenderCaps.java
  58. 684 0
      app/src/main/java/min3d/core/Renderer.java
  59. 218 0
      app/src/main/java/min3d/core/RendererActivity.java
  60. 297 0
      app/src/main/java/min3d/core/Scene.java
  61. 155 0
      app/src/main/java/min3d/core/TextureList.java
  62. 165 0
      app/src/main/java/min3d/core/TextureManager.java
  63. 137 0
      app/src/main/java/min3d/core/UvBufferList.java
  64. 183 0
      app/src/main/java/min3d/core/Vertices.java
  65. 8 0
      app/src/main/java/min3d/interfaces/IDirtyManaged.java
  66. 6 0
      app/src/main/java/min3d/interfaces/IDirtyParent.java
  67. 20 0
      app/src/main/java/min3d/interfaces/IObject3dContainer.java
  68. 39 0
      app/src/main/java/min3d/interfaces/ISceneController.java
  69. 114 0
      app/src/main/java/min3d/objectPrimitives/Box.java
  70. 142 0
      app/src/main/java/min3d/objectPrimitives/HollowCylinder.java
  71. 57 0
      app/src/main/java/min3d/objectPrimitives/Rectangle.java
  72. 106 0
      app/src/main/java/min3d/objectPrimitives/SkyBox.java
  73. 127 0
      app/src/main/java/min3d/objectPrimitives/Sphere.java
  74. 115 0
      app/src/main/java/min3d/objectPrimitives/Torus.java
  75. 429 0
      app/src/main/java/min3d/parser/AParser.java
  76. 27 0
      app/src/main/java/min3d/parser/IParser.java
  77. 157 0
      app/src/main/java/min3d/parser/LittleEndianDataInputStream.java
  78. 252 0
      app/src/main/java/min3d/parser/MD2Parser.java
  79. 241 0
      app/src/main/java/min3d/parser/Max3DSParser.java
  80. 286 0
      app/src/main/java/min3d/parser/ObjParser.java
  81. 165 0
      app/src/main/java/min3d/parser/ParseObjectData.java
  82. 12 0
      app/src/main/java/min3d/parser/ParseObjectFace.java
  83. 58 0
      app/src/main/java/min3d/parser/Parser.java
  84. 32 0
      app/src/main/java/min3d/vos/AbstractDirtyManaged.java
  85. 24 0
      app/src/main/java/min3d/vos/BooleanManaged.java
  86. 19 0
      app/src/main/java/min3d/vos/CameraVo.java
  87. 100 0
      app/src/main/java/min3d/vos/Color4.java
  88. 202 0
      app/src/main/java/min3d/vos/Color4Managed.java
  89. 29 0
      app/src/main/java/min3d/vos/Face.java
  90. 24 0
      app/src/main/java/min3d/vos/FloatManaged.java
  91. 21 0
      app/src/main/java/min3d/vos/FogType.java
  92. 99 0
      app/src/main/java/min3d/vos/FrustumManaged.java
  93. 228 0
      app/src/main/java/min3d/vos/Light.java
  94. 20 0
      app/src/main/java/min3d/vos/LightType.java
  95. 165 0
      app/src/main/java/min3d/vos/Number3d.java
  96. 139 0
      app/src/main/java/min3d/vos/Number3dManaged.java
  97. 26 0
      app/src/main/java/min3d/vos/RenderType.java
  98. 23 0
      app/src/main/java/min3d/vos/ShadeModel.java
  99. 25 0
      app/src/main/java/min3d/vos/ShadeModelManaged.java
  100. 34 0
      app/src/main/java/min3d/vos/TexEnvxVo.java

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

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="app.mar.activities">
+
+    <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="@drawable/logo"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        tools:replace="android:icon">
+        <activity android:name="app.mar.activities.ARActivity" />
+        <activity
+            android:name="app.mar.activities.ImageViewerActivity"
+            android:theme="@style/AppTheme.NoActionBar.Fullscreen" />
+        <activity
+            android:name="app.mar.activities.ResourceListActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:label="@string/title_activity_resource_list"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="app.mar.activities.SettingsActivity"
+            android:configChanges="orientation|keyboardHidden"
+            android:label="@string/title_activity_settings"
+            android:screenOrientation="portrait" />
+        <activity android:name="app.mar.activities.TransferActivity" />
+        <activity android:name="app.mar.activities.EndActivity" />
+        <activity
+            android:name="app.mar.activities.MARMenuActivity"
+            android:configChanges="orientation|keyboardHidden"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="app.mar.activities.SEMenuActivity"
+            android:configChanges="orientation|keyboardHidden"
+            android:screenOrientation="portrait">
+            >
+        </activity>
+        <activity
+            android:name="app.mar.activities.PuzzleActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:label="@string/title_activity_puzzle"
+            android:theme="@style/FullscreenTheme" />
+        <activity android:name="app.mar.activities.PermissionActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="app.mar.activities.MediaViewerAcitvity"
+            android:theme="@style/AppTheme.NoActionBar.Fullscreen" />
+        <activity
+            android:name="app.mar.activities.ModelViewerActivity"
+            android:theme="@style/AppTheme.NoActionBar.Fullscreen" />
+        <activity
+            android:name="app.mar.activities.InfoActivity"
+            android:theme="@style/AppTheme.NoActionBar.Fullscreen" />
+        <activity android:name="app.mar.activities.CreditsActivity"
+            android:theme="@style/AppTheme.NoActionBar.Fullscreen" ></activity>
+    </application>
+</manifest>

+ 606 - 0
app/src/main/java/app/mar/activities/ARActivity.java

@@ -0,0 +1,606 @@
+package app.mar.activities;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+
+import app.mar.ui.CameraPreview;
+import app.mar.utils.AndroidResources;
+import app.mar.utils.Settings;
+import app.mar.utils.game.Area;
+import app.mar.utils.game.Game;
+import app.mar.utils.game.LocatedResources;
+import app.mar.utils.game.Place;
+import app.mar.utils.game.Resource;
+import app.mar.utils.game.Stage;
+import app.mar.utils.geometry.GPSPoint;
+import min3d.core.Object3d;
+import min3d.core.Object3dContainer;
+import min3d.core.RendererActivity;
+import min3d.vos.CameraVo;
+import min3d.vos.Light;
+import min3d.vos.LightType;
+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 final Object mLock = new Object();
+    protected boolean isPausing=false;
+    protected static GPSPoint mLocation;
+    protected static boolean mLocationUpdated=false;
+    protected TextView mTvDistance;
+    protected TextView mTvAngle;
+    protected TextView mTvGPS;
+    protected RelativeLayout mRlRoot;
+    protected ImageView mIvWhite;
+    protected ImageView mIvRed;
+    protected ImageView mIvGreen;
+    protected ImageView mIvNone;
+    protected int mViewWidth;
+    protected int mViewHeight;
+    protected boolean mDetect=false;
+    protected boolean mInArea=false;
+    private Game mGame;
+    private  Boolean  mUpdate = true;
+    private Handler mCustomHandler = new Handler();
+
+    private boolean mIsResource = false;
+
+    public void init()
+    {
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+
+
+        cameraPreviewLayout = (FrameLayout)findViewById(R.id.camera_preview);
+        camera = checkDeviceCamera();
+        mImageSurfaceView = new CameraPreview(ARActivity.this, camera);
+
+
+        mOkButton = (Button) findViewById(R.id.button_catch);
+        mOkButton.setVisibility(View.INVISIBLE);
+        if(cameraPreviewLayout.getChildCount()<2)
+        {
+            cameraPreviewLayout.addView(_glSurfaceView,0);
+            cameraPreviewLayout.addView(mImageSurfaceView,0);
+        }
+
+    }
+
+    private Camera checkDeviceCamera() {
+        Camera mCamera = null;
+        try {
+            mCamera = Camera.open();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mCamera;
+    }
+
+    protected void onCreateSetContentView()
+    {
+        //init();
+
+    }
+
+    @Override
+    protected void glSurfaceViewConfig()
+    {
+        // !important
+        _glSurfaceView.setEGLConfigChooser(8,8,8,8, 16, 0);
+        _glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+    }
+
+    @Override
+    public void initScene()
+    {
+        // !important
+        scene.clear();
+        scene.backgroundColor().setAll(0x00000000);
+        //scene.lights().add(new Light());
+        //scene.lights().add(new Light());
+
+        Light myLight = new Light();
+        myLight.position.setZ(0);
+        myLight.position.setY(100);
+        myLight.type(LightType.POSITIONAL);
+        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))
+        );
+    }
+
+    private Runnable updateTimerThread = new Runnable() {
+
+        public void run() {
+            mCustomHandler.postDelayed(this, 10);
+        }
+
+    };
+
+    protected long mLedPeriode=0;
+    protected long mLedLastTick=0;
+    protected boolean mLedState=false;
+    protected void updateLed(boolean detected,  double dist)
+    {
+        //si detecte
+        if(detected)
+        {
+            mIvWhite.getHandler().post(new Runnable() { public void run() {mIvWhite.setVisibility(View.VISIBLE);}});
+            mIvGreen.getHandler().post(new Runnable() { public void run() {mIvGreen.setVisibility(View.VISIBLE);}});
+            mIvRed.getHandler().post(new Runnable() { public void run() {mIvRed.setVisibility(View.INVISIBLE);}});
+            mInArea=mDetect=true;
+        }
+        //si dans une zone
+        else if(dist>=0)
+        {
+            if(dist<5) mLedPeriode=5;
+            else if(dist>=5 && dist<50) mLedPeriode=(int)((dist-4)*10);
+            else mLedPeriode=500;
+            if(System.currentTimeMillis()>mLedLastTick+mLedPeriode)
+            {
+                mLedLastTick=System.currentTimeMillis();
+                if(mLedState)
+                {
+                    mLedState=false;
+                    mIvWhite.getHandler().post(new Runnable() { public void run() {mIvWhite.setVisibility(View.INVISIBLE);}});
+                }else
+                {
+                    mLedState=true;
+                    mIvWhite.getHandler().post(new Runnable() { public void run() {mIvWhite.setVisibility(View.VISIBLE);}});
+                }
+            }
+
+            mIvGreen.getHandler().post(new Runnable() { public void run() {mIvGreen.setVisibility(View.INVISIBLE);}});
+            mIvRed.getHandler().post(new Runnable() { public void run() {mIvRed.setVisibility(View.VISIBLE);}});
+            mInArea=true;
+            mDetect=false;
+        }
+        //si rien
+        else
+        {
+            mIvWhite.getHandler().post(new Runnable() { public void run() {mIvWhite.setVisibility(View.INVISIBLE);}});
+            mIvGreen.getHandler().post(new Runnable() { public void run() {mIvGreen.setVisibility(View.INVISIBLE);}});
+            mIvRed.getHandler().post(new Runnable() { public void run() {mIvRed.setVisibility(View.INVISIBLE);}});
+            mInArea=mDetect=false;
+        }
+    }
+    protected String updateArSceneResource(Resource rr, Place p, String toDisplay)
+    {
+        Object3d oo = rr.get3DModel(this);
+        CameraVo v = new CameraVo();
+
+        if(mGame.getmSettings().isARMode()) {
+            double distCoef = (p.getDistance(mGame.getPlayer()) / 50) * 0.8;
+            distCoef += 0.2;
+            double x = 0;
+
+
+            if (distCoef > 1) distCoef = 1.0;
+
+            double theta = -(-mGame.getPlayer().getOrientationY() + 135 - (0.5 * Math.atan(p.getDistance(mGame.getPlayer()) / 1.6) * 180 / Math.PI) % 360);
+            while (theta < -180) theta += 360;
+            while (theta > 180) theta -= 360;
+            oo.position().y = -25 + (float) (50 * Math.sin(-theta * Math.PI / 180.0));
+
+            double alpha = ((float) (mGame.getPlayer().getAngleWith(p))-mGame.getPlayer().getOrientationX() );// - mGame.getPlayer().getOrientationZ();
+            oo.position().z = -(float)(90*distCoef*Math.cos(alpha*Math.PI/180.0));
+            oo.position().x = -(float) (90 * distCoef * Math.sin(alpha * Math.PI / 180.0));
+
+
+
+            if (mGame.getmSettings().isAreaDebug()) {
+
+                toDisplay += "Azimuth: " + dts(alpha) + "°\nAngle:"+mGame.getPlayer().getAngleWith(p) +"\nPosition (" + dts(oo.position().x) + ", " + dts(oo.position().y)
+                        + ", " + dts(oo.position().z) + ")\nAngle Rel:"+dts(alpha)+" : "+dts(mGame.getPlayer().getAngleWith(p))+" - "+dts(mGame.getPlayer().getOrientationX())+"\n";
+                toDisplay+=" oo = { "+oo.position().x+", "+oo.position().y+", "+oo.position().z+" }\n";
+            }
+        }else
+        {
+            oo.position().y = 0;
+            oo.position().z = -20;
+            oo.position().x = 0;
+        }
+
+        //vX = (float) (mGame.getPlayer().getAngleWith(p));
+        scene.camera(v);
+        scene.addChild(oo);
+        return toDisplay;
+    }
+
+
+
+    protected boolean updateSceneResource()
+    {
+
+        ArrayList<LocatedResources> res = mGame.getResourcesNextToPlayer();
+        String toDisplay ="";
+        boolean detected = false;
+        boolean isAR = mGame.getmSettings().isARMode();
+        // String dete = "Detected:\n";
+        scene.clear();
+
+
+       // if(mGame.getmSettings().isAreaDebug())
+        {
+            ArrayList<Area> ar = mGame.getAreaNextToPlayer();
+            String ttxt = "Zones trouvées:\n";
+            for(int i=0; i<ar.size(); i++)
+                ttxt+="\t"+ar.get(i).getName()+"\n";
+            toDisplay+=ttxt+"\n\n";
+            String str = "Stage : "+mGame.getCurrentStage()+"\nZones les plus proches ("+mGame.getCurrentStageAreas().size()+"):\n"+ "--->";
+            ArrayList<Area> arr = mGame.getCurrentStageAreas();
+            for(int i=0; i<arr.size(); i++)
+                if(mGame.getPlayer().getPosition()!=null)
+                    str+="\t"+arr.get(i).getName()+" : "+arr.get(i).getDistanceToNextPlace(mGame.getPlayer())+" m\n";
+                else
+                    str+="\t"+arr.get(i).getName()+" : ?\n";
+            toDisplay+=str+"\n";
+
+        }
+
+        if(mGame.getmSettings().isAreaDebug()) toDisplay+="Resources détectées: \n";
+
+        for (int i = 0; i < res.size(); i++) {
+            Resource rr = res.get(i).getResource(0);
+            Place p = res.get(i).getPlace();
+            if (rr != null) {
+                detected = true;
+                if(isAR) mGame.getPlayer().freezePosition(p.getLocation());
+                toDisplay=updateArSceneResource(rr, p, toDisplay);
+            }
+        }
+        if(isAR && res.size()==0) mGame.getPlayer().releasePosition();
+
+        updateLed(detected, mGame.getResourcesNextToPlayerDistDouble());
+
+
+
+        //affichage en debug
+        final String td = toDisplay;
+        final TextView xv = (TextView)findViewById(R.id.text_area);
+        xv.getHandler().post(new Runnable() { public void run() { xv.setText(td);  } });
+
+
+        //affichage de la distance dans l'HUD
+        final String dete2 = mGame.getResourcesNextToPlayerDist();
+        mTvDistance.getHandler().post(new Runnable() {
+            public void run() {
+                mTvDistance.setText(dete2);
+            }
+        });
+
+        if(mGame.getmSettings().isARMode())
+            return scene.numChildren()>0;
+        else
+            return res.size()>0;
+    }
+
+    protected void onList()
+    {
+        Intent intent = new Intent(this, ARActivity.class);
+        intent.putExtra("game", mGame);
+        startActivity(intent);
+    }
+
+    protected void moveView(View v, int x, int y, int w, int h)
+    {
+        RelativeLayout.LayoutParams l = new RelativeLayout.LayoutParams(w, h);
+        l.leftMargin = x;
+        l.topMargin = y;
+        v.setLayoutParams(l);
+    }
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        setContentView(R.layout.activity_ar);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        //mGame = (Game) getIntent().getSerializableExtra("game");
+        mGame = Game.game();
+        mGame.newSensorManager(this);
+        mTvDistance=(TextView)findViewById(R.id.tv_distance);
+        mTvAngle=(TextView)findViewById(R.id.tv_angle);
+        mTvGPS=(TextView)findViewById(R.id.tv_gps);
+        mRlRoot=(RelativeLayout) findViewById(R.id.rl_root);
+        mIvWhite=(ImageView)findViewById(R.id.iv_white);
+        mIvRed=(ImageView)findViewById(R.id.iv_red);
+        mIvGreen=(ImageView)findViewById(R.id.iv_green);
+        mIvNone=(ImageView)findViewById(R.id.iv_none);
+
+
+
+        ViewTreeObserver vto = mRlRoot.getViewTreeObserver();
+        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                mRlRoot.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+                int w =mViewWidth  = mRlRoot.getMeasuredWidth();
+                int h =mViewHeight = mRlRoot.getMeasuredHeight();
+
+                mTvGPS.setTextSize(pxToDp((int)(h*0.100f/3.5)));
+                mTvGPS.setText("N ?°\nE ?°");
+                mTvAngle.setTextSize(pxToDp((int)(h*0.121f/3.5)));
+                mTvDistance.setTextSize(pxToDp((int)(h*0.046f)));
+
+                moveView(mTvAngle, (int)(0.03*w), (int)(0.864f*h), (int)(0.291f*w), (int)(0.121*h ));
+                moveView(mTvGPS, w-(int)(0.32f*w), (int)(0.875f*h),(int)(0.3*w), (int)(0.121*h));
+                moveView(mTvDistance, w-mTvDistance.getWidth()-(int)(0.36f*w), (int)(0.033f*h), mTvGPS.getWidth(), (int)(0.121*h));
+
+                moveView(mIvNone, (int)(w/4.0), 0, (int)(w/2.0), (int)(0.06f*h));
+                moveView(mIvWhite, (int)(w*0.269), (int)(0.034*h), (int)(w*0.03333), (int)(0.02286f*h));
+                moveView(mIvRed, (int)(w*0.702), (int)(0.034*h), (int)(w*0.03333), (int)(0.02286f*h));
+                moveView(mIvGreen, (int)(w*0.702), (int)(0.034*h), (int)(w*0.03333), (int)(0.02286f*h));
+            }
+        });
+
+
+
+        int w = mRlRoot.getWidth();
+        int h = mRlRoot.getHeight();
+
+        mGame.printStage();
+
+    }
+
+
+
+
+    @Override
+    /**
+     * Verifie si on doit afficher des resources et gere le bouton de capture
+     */
+    public  void updateScene()
+    {
+        synchronized (mUpdate)
+        {
+            if(!mUpdate) return;
+
+            //Precache du modèle 3D
+            synchronized (mLock) {
+                if (!isPausing) {
+                    boolean b = mGame.precache3DResource(this);
+                    mIsResource = updateSceneResource();
+                }else
+                {
+                    scene.clear();
+                    mGame.removeCached3DModels();
+                }
+            }
+
+            if(mIsResource) {
+                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);
+                    }
+                });
+
+            }
+            mTvAngle.getHandler().post(new Runnable() {
+                public void run() {
+                    NumberFormat formatter = new DecimalFormat("#0.0");
+                    /*mTvAngle.setText("X : "+formatter.format(mGame.getPlayer().getOrientation())+"°\nY : "+
+                            formatter.format(mGame.getPlayer().getOrientationY())
+                            +"°\nZ : "+formatter.format(mGame.getPlayer().getOrientationZ())+"°");*/
+
+
+                }
+            });
+
+            setGpsText();
+        }
+
+    }
+
+    protected String dts(double d, int n)
+    {
+        String str = "#0";
+        if(n>0)
+        {
+            str+=".";
+            for(int i=0; i<n; i++) str+="0";
+        }
+        NumberFormat formatter = new DecimalFormat(str);
+        return formatter.format(d);
+    }
+
+    protected String dts(double d)
+    {
+        return dts(d,2);
+    }
+
+
+    @Override
+    protected  void onResume() {
+        super.onResume();
+        init();
+        mGame.onResume(this);
+        scene.clear();
+        synchronized (mLock)
+        {
+            isPausing=false;
+        }
+    }
+    @Override
+    protected  void onPause()
+    {
+        synchronized (mLock)
+        {
+            isPausing=true;
+        }
+        super.onPause();
+        mGame.onPause(this);
+        scene.clear();
+        cameraPreviewLayout.removeViewAt(0);
+        cameraPreviewLayout.removeViewAt(0);
+
+    }
+
+    public void finish()
+    {
+        mGame.stopSensors();
+        super.finish();
+    }
+
+    public void setGpsText()
+    {
+        if(mLocationUpdated) {
+            final GPSPoint gps = mLocation;
+            mTvGPS.getHandler().post(new Runnable() {
+
+                public void run() {
+
+                    NumberFormat formatter = new DecimalFormat("#0.0000000");
+                    NumberFormat formatter2 = new DecimalFormat("#0.0");
+                    mTvGPS.setText("N "+formatter.format(gps.getX()) + "°\nE " + formatter.format(gps.getY()) + "°\n+/- "+formatter2.format(gps.getAccuracy())+" m");
+                }
+            });
+            mLocationUpdated=false;
+        }
+    }
+
+
+    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)
+    {
+        synchronized (mUpdate) {mUpdate=false;}
+        Stage s = mGame.getCurrentStageObj();
+        ArrayList<Resource> res = null;
+        boolean stageFinished = mGame.pickResource();
+        boolean finished = false;
+        res=mGame.getPickedUpResources();
+
+
+        if(stageFinished)
+        {
+            finished = mGame.nextStage();
+        }
+
+        if(finished)
+        {
+            Log.e("Finished", "Finished");
+        }
+
+        if(stageFinished)
+        {
+            Resource stageRes = s.getResource();
+
+            if(finished)
+            {
+                Intent intent2 = new Intent(this, EndActivity.class);
+                startActivity(intent2);
+            }
+            System.out.println("Ressource: '"+s.getResourceName()+"' : "+!s.getResourceName().isEmpty());
+            if(!s.getResourceName().isEmpty() && s.isTransition())
+            {
+                Intent intent = AndroidResources.getViewerIntent(this, stageRes);
+                startActivity(intent);
+            }
+
+            System.out.println("Transition: '"+s.isTransition());
+            if(s.isTransition())
+            {
+                Intent intent2 = new Intent(this, TransferActivity.class);
+                startActivity(intent2);
+            }
+        }
+        for(int i=0; i<res.size(); i++)
+        {
+            Intent intent = AndroidResources.getViewerIntent(this, res.get(i));
+            startActivity(intent);
+        }
+        finish();
+    }
+
+
+    public static void setLocation(GPSPoint p)
+    {
+        mLocation = p;
+        mLocationUpdated=true;
+    }
+
+
+    public static int dpToPx(int dp)
+    {
+        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+    }
+
+    public static int pxToDp(int px)
+    {
+        return (int) (px / Resources.getSystem().getDisplayMetrics().density);
+    }
+
+
+
+    public void onClickScreen(View v)
+
+    {
+        if(mIsResource)
+            onClickPickUp(v);
+        else if(Settings.getSettings().isGPSDebug())
+            mGame.getPlayer().nextStub();
+        else {
+
+        }
+    }
+
+}

+ 55 - 0
app/src/main/java/app/mar/activities/CreditsActivity.java

@@ -0,0 +1,55 @@
+package app.mar.activities;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.text.Html;
+import android.widget.TextView;
+
+import app.mar.utils.FontChangeCrawler;
+
+public class CreditsActivity extends AppCompatActivity {
+
+    private TextView mText;
+    private static final String CREDITS="<h1> Credits </h1>\n" +
+            "Ce jeux a été réalisé par les CE2 et CM1 de l'école Les Colombes de Saint Erblon durant les temps méridiens (encadré par François Gautrais).\n" +
+            "\n" +
+            "<h2> Scénario </h2>\n" +
+            "- Romain<br>\n" +
+            "- Tiffen<br>\n" +
+            "- Léa <br>\n" +
+            "- Thaïs<br>\n" +
+            "- Jules <br>\n" +
+            "\n" +
+            "<h2> Réalisation des indices </h2>\n" +
+            "- Romain<br>\n" +
+            "- Tiffen<br>\n" +
+            "- Léa<br>\n" +
+            "- Thaïs<br>\n" +
+            "- François Gautrais<br>\n" +
+            "\n" +
+            "<h2> Actteurs-ices </h2>\n" +
+            "- Romain : Le chasseur de monstre<br>\n" +
+            "- Léa : Qui se balladait place de la fontaine<br>\n" +
+            "- Élise : Qui se balladait aux étangs<br>\n" +
+            "- Jules : À la bibliothèque<br>\n" +
+            "- Sophia : Au parc de l'Ise<br>\n" +
+            "\n" +
+            "<h2> Programmation / Interface / Montage </h2>\n" +
+            "- François Gautrais<br>\n" +
+            "\n";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_credits);
+        mText = (TextView) findViewById(R.id.tv_credits);
+        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N) {
+            mText.setText(Html.fromHtml(CREDITS));
+        }else
+        {
+            mText.setText(Html.fromHtml(CREDITS, Html.FROM_HTML_MODE_COMPACT));
+        }
+
+        FontChangeCrawler.setFont(this);
+    }
+}

+ 16 - 0
app/src/main/java/app/mar/activities/EndActivity.java

@@ -0,0 +1,16 @@
+package app.mar.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import app.mar.utils.FontChangeCrawler;
+
+public class EndActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_end);
+        FontChangeCrawler.setFont(this);
+    }
+}

+ 7 - 0
app/src/main/java/app/mar/activities/IViewerActivity.java

@@ -0,0 +1,7 @@
+package app.mar.activities;
+
+/**
+ * Created by ptitcois on 04/07/17.
+ */
+public interface IViewerActivity {
+}

+ 124 - 0
app/src/main/java/app/mar/activities/ImageViewerActivity.java

@@ -0,0 +1,124 @@
+package app.mar.activities;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import app.mar.utils.AndroidResources;
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Resource;
+import app.mar.utils.files.FileManager;
+import uk.co.senab.photoview.PhotoViewAttacher;
+
+/**
+ * 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 ImageViewerActivity extends AppCompatActivity implements IViewerActivity{
+
+
+    private Resource mResource;
+    private ImageView mImageView;
+    private Button mButton;
+    private PhotoViewAttacher mAttacher;
+
+
+    public static final int CONTENT_NULL=0;
+    public static final int CONTENT_MAP=1;
+    public static final int CONTENT_INFO=2;
+    public static final int CONTENT_IMAGE=3;
+    public static final int CONTENT_3D=4;
+    protected int mContent=CONTENT_NULL;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_viewer);
+        mImageView = (ImageView) findViewById(R.id.imageView);
+        mButton = (Button) findViewById(R.id.info);
+        if(getIntent().hasExtra("resource"))
+        {
+            mResource = (Resource) getIntent().getSerializableExtra("resource");
+            if(mResource!=null) setTitle(mResource.getTitle());
+        }
+
+        final TextView title = (TextView) findViewById(R.id.title);
+        title.setVisibility(View.VISIBLE);
+
+        if(mResource != null && !getIntent().hasExtra("information") && !getIntent().hasExtra("map"))
+        {
+            Log.e("__________", "Info loaded");
+            title.setText(mResource.getTitle());
+            if(mResource.isImage())
+            {
+                mContent=CONTENT_IMAGE;
+                Bitmap bmp=null;
+                Toast.makeText(this, "Chargement de '"+mResource.getName()+"'", Toast.LENGTH_SHORT).show();
+                try {
+                    bmp = FileManager.openImage(this, mResource.getName());
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Toast.makeText(this, "Impossible de charger '"+mResource.getName()+"'", Toast.LENGTH_LONG).show();
+                }
+
+                mImageView.setImageBitmap(bmp);
+                mAttacher = new PhotoViewAttacher(mImageView);
+                mAttacher.setMaximumScale(20);
+
+            }
+
+        } else if(getIntent().hasExtra("map") )
+        {
+            Log.e("__________", "Map loaded");
+            mContent=CONTENT_MAP;
+            Bitmap bmp=null;
+            try {
+                bmp = FileManager.openImage(this, "map");
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            title.setVisibility(View.GONE);
+            mImageView.setImageBitmap(bmp);
+            mAttacher = new PhotoViewAttacher(mImageView);
+            mAttacher.setMaximumScale(20);
+            mButton.setVisibility(View.GONE);
+        }else
+        {
+            Log.e("__________", "Erreur");
+            title.setText("Erreur");
+        }
+        FontChangeCrawler.setFont(this);
+
+    }
+
+    public void onClickInfo(View v)
+    {
+        Intent intent = AndroidResources.getInfoIntent(this, mResource);
+        startActivity(intent);
+    }
+
+
+    public void onClick(View v)
+    {
+        if(mContent==CONTENT_IMAGE) {
+
+            final TextView title = (TextView) findViewById(R.id.title);
+            if (title.getVisibility() == View.VISIBLE) {
+                title.setVisibility(View.GONE);
+                mButton.setVisibility(View.GONE);
+            } else {
+
+                title.setVisibility(View.VISIBLE);
+                mButton.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+}

+ 37 - 0
app/src/main/java/app/mar/activities/InfoActivity.java

@@ -0,0 +1,37 @@
+package app.mar.activities;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Resource;
+
+public class InfoActivity extends AppCompatActivity  implements IViewerActivity{
+
+    private Resource mResource;
+    private TextView mTitle;
+    private TextView mText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_info);
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mText = (TextView) findViewById(R.id.tv_comment);
+
+        if(getIntent().hasExtra("resource"))
+        {
+            mResource = (Resource) getIntent().getSerializableExtra("resource");
+            if(mResource!=null) setTitle(mResource.getTitle());
+            mTitle.setText(mResource.getTitle());
+            mText.setText(mResource.getComment());
+        }else
+        {
+            mTitle.setText("Error: Res not found");
+        }
+        FontChangeCrawler.setFont(this);
+
+    }
+}

+ 62 - 0
app/src/main/java/app/mar/activities/MARMenuActivity.java

@@ -0,0 +1,62 @@
+package app.mar.activities;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.widget.Button;
+
+/**
+ * Created by ptitcois on 06/06/17.
+ */
+public class MARMenuActivity extends MenuActivity{
+    protected  void refreshMenuStyle()
+    {
+        mRootLayout.setBackgroundResource(R.drawable.background);
+        int h4 = mHeight/3;
+        int w4 = mWidth/3;
+        int w = (int)(w4*1.25);
+        int h = dpToPx((int)(mBBriefing.getTextSize()*1.5));
+
+        moveView(mBBriefing, w4-2*w/3, mHOffset+h4-h,w,h);
+        moveView(mBInvotory, 2*w4-w/3, mHOffset+h4-h,w,h);
+        moveView(mBMap, w4-2*w/3, mHOffset+2*h4,w,h);
+        moveView(mBOptions, 2*w4-w/3, mHOffset+2*h4,w,h);
+        moveView(mBSend, mWidth/2-w, mHOffset+2*h4+4*h/3, 2*w, h);
+        ((Button)mBMenu).setTextSize(25);
+        //mBMenu.setTypeface(null,Typeface.BOLD);
+        sendVisibility();
+
+    }
+
+
+
+    protected void setUpMenuStyle()
+    {
+        mIbScanner = new Button(this);
+        //mIbScanner.setImageResource(R.drawable.scanner);
+        //mIbScanner.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        mRootLayout.addView(mIbScanner);
+        mIbScanner.setBackgroundResource(R.drawable.uibuttoncircle);
+        mIbScanner.setText("Scanner");
+        mIbScanner.setTextColor(getResources().getColor(R.color.dull_4));
+        mIbScanner.setTextSize(22);
+        mIbScanner.setOnClickListener(this);
+
+        moveView(mIbScanner, mWidth / 2 - mHeight / 8, mHOffset + mHeight / 2 - mHeight / 8, mHeight / 4, mHeight / 4);
+
+        mBBriefing = newButton("Briefing");
+        mBInvotory = newButton("Inventaire");
+        mBMap = newButton("Carte");
+        mBOptions = newButton("Options");
+        mBMenu = newButton("Menu Principal");
+        mBMenu.setBackgroundResource(R.drawable.title);
+        mBSend = newButton("Envoyer les données");
+        Resources r = getResources();
+        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, r.getDisplayMetrics());
+        moveView(mBMenu, 0, 0, mWidth, (int) px);
+
+        //mGame.pickResource("intro", false);
+        //mGame.pickResource("a", false);
+        mGame.pickUpOtherResources();
+
+    }
+}

+ 231 - 0
app/src/main/java/app/mar/activities/MediaViewerAcitvity.java

@@ -0,0 +1,231 @@
+package app.mar.activities;
+
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import app.mar.ui.MediaView;
+import app.mar.utils.AndroidResources;
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Resource;
+
+public class MediaViewerAcitvity extends AppCompatActivity implements MediaPlayer.OnSeekCompleteListener,
+                                                                MediaView.OnStartListener,
+                                                                SeekBar.OnSeekBarChangeListener,
+                                                                IViewerActivity{
+    private Resource mResource;
+    private Button mInfo;
+    private TextView mTitle;
+
+    private FrameLayout mFrameLayout;
+    private ImageButton  mButton;
+    private MediaView mMediaView;
+    private ImageView mImageView;
+    private SeekBar mBar;
+    private Handler mHandler = new Handler();
+    private AutoSeek mAutoSeek;
+    private int mCurrentTime;
+    private int mEndTime;
+    private boolean mUserSeeks=false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_media);
+
+        mFrameLayout = (FrameLayout) findViewById(R.id.media_viewer);
+        mButton = (ImageButton) findViewById(R.id.play);
+        mInfo = (Button) findViewById(R.id.info);
+        mBar = (SeekBar) findViewById(R.id.seekBar);
+        mBar.setOnSeekBarChangeListener(this);
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mAutoSeek = new AutoSeek(mHandler);
+
+        mMediaView = new MediaView(this);
+        mMediaView.setOnStartListener(this);
+        mImageView = new ImageView(this);
+        mImageView.setImageResource(R.drawable.audio);
+
+        mFrameLayout.addView(mMediaView);
+        mFrameLayout.addView(mImageView);
+
+
+        if(getIntent().hasExtra("resource"))
+        {
+            mResource = (Resource) getIntent().getSerializableExtra("resource");
+            if(mResource==null) return;
+
+            mTitle.setText(mResource.getTitle());
+
+            String path=mResource.getName();
+            if(mResource.isAudio())
+            {
+                path+="_audio";
+                mImageView.setVisibility(View.VISIBLE);
+            }
+            else
+            {
+                path+="_video";
+                mImageView.setVisibility(View.GONE);
+            }
+
+            mMediaView.play(path);
+        }
+        FontChangeCrawler.setFont(this);
+
+
+    }
+
+    public void onFullscreen(View v)
+    {
+        if(mTitle.getVisibility()==View.VISIBLE)
+        {
+            mTitle.setVisibility(View.GONE);
+            mInfo.setVisibility(View.GONE);
+        }else
+        {
+            mTitle.setVisibility(View.VISIBLE);
+            mInfo.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public void onPlayPause(View v)
+    {
+        if(mMediaView.isPlaying())
+        {
+            mAutoSeek.stop();
+            mButton.setImageResource(android.R.drawable.ic_media_play);
+            mMediaView.pause();
+        }else
+        {
+            mAutoSeek.start();
+            mButton.setImageResource(android.R.drawable.ic_media_pause);
+            mMediaView.start();
+        }
+    }
+
+    public void onInfos(View v)
+    {
+        Intent intent = AndroidResources.getInfoIntent(this, mResource);
+        startActivity(intent);
+    }
+
+    @Override
+    public void onSeekComplete(MediaPlayer mediaPlayer) {
+        mAutoSeek.start();
+        mButton.setImageResource(android.R.drawable.ic_media_pause);
+    }
+
+
+
+    public void onUpdateSeek()
+    {
+        if(mMediaView.isPlaying())
+        {
+            mCurrentTime=mMediaView.getTime()/1000;
+            mBar.setProgress(mCurrentTime);
+        }
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+        mUserSeeks=b;
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+
+    }
+
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+        if(mUserSeeks)
+        {
+            mCurrentTime=seekBar.getProgress();
+            mMediaView.seek(mCurrentTime*1000);
+            mAutoSeek.stop();
+            mUserSeeks=false;
+        }
+    }
+
+    @Override
+    public void onStart(MediaView mv) {
+        mCurrentTime=mv.getTime()/1000;
+        mEndTime=mv.getDuration()/1000;
+
+        mBar.setMax(mEndTime);
+        mBar.setProgress(mCurrentTime);
+        mMediaView.setOnSeekCompleteListener(this);
+
+        mAutoSeek.start();
+        mButton.setImageResource(android.R.drawable.ic_media_pause);
+    }
+
+    private class AutoSeek implements Runnable
+    {
+        private Handler mHandler;
+        AutoSeek(Handler h)
+        {
+            mHandler=h;
+        }
+
+        @Override
+        public void run() {
+            onUpdateSeek();
+            mHandler.postDelayed(this, 500);
+        }
+
+        public void start()
+        {
+            mHandler.postDelayed(this, 500);
+        }
+
+
+        public void stop()
+        {
+            mHandler.removeCallbacks(this);
+        }
+    }
+
+    protected void onResume()
+    {
+        super.onResume();
+    }
+
+    protected  void onPause()
+    {
+        mMediaView.stop();
+        mAutoSeek.stop();
+        super.onPause();
+
+    }
+
+    public void finish()
+    {
+
+        mMediaView.stop();
+        mAutoSeek.stop();
+        mMediaView.clearPlayer();
+        super.finish();
+    }
+
+    public void onDestroy()
+    {
+        mMediaView.stop();
+        mAutoSeek.stop();
+        mMediaView.clearPlayer();
+        super.onDestroy();
+    }
+
+}

+ 241 - 0
app/src/main/java/app/mar/activities/MenuActivity.java

@@ -0,0 +1,241 @@
+package app.mar.activities;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+import app.mar.ui.RotateButton;
+import app.mar.utils.AndroidResources;
+import app.mar.utils.Settings;
+import app.mar.utils.game.Game;
+import app.mar.utils.game.Resource;
+import app.mar.utils.game.Stage;
+
+public abstract class  MenuActivity extends Activity implements View.OnClickListener, IViewerActivity{
+
+    protected int mWidth;
+    protected int mHeight;
+    protected Game mGame;
+
+    protected RelativeLayout mRootLayout;
+    protected Handler mCustomHandler = new Handler();
+    //private final int COLOR_BACKGROUND = ;
+    protected final int COLOR_SHAPE = R.color.dull_2;
+    protected final int COLOR_BACKGROUND_SECOND = R.color.dull_3;
+    protected final int COLOR_OTHER = R.color.dull_4;
+    protected final int COLOR_TEXT = R.color.dull_5;
+
+    protected Button mIbScanner;
+    protected Button mBBriefing;
+    protected Button mBInvotory;
+    protected Button mBMap;
+    protected Button mBOptions;
+    protected View mBMenu;
+    protected Button mBSend;
+    protected int mHOffset=0;
+
+    protected  void refreshMenuStyle(){}
+
+    protected void setUpMenuStyle(){}
+
+    private void updateSize()
+    {
+        Display display = getWindowManager().getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+        mWidth = size.x;
+        mHeight = size.y;
+        mHOffset=mHeight/15;
+    }
+
+    protected void moveView(View v, int x, int y, int w, int h)
+    {
+        RelativeLayout.LayoutParams l = new RelativeLayout.LayoutParams(w, h);
+        l.leftMargin = x;
+        l.topMargin = y;
+        v.setLayoutParams(l);
+    }
+
+    protected void moveView(View v, int x, int y)
+    {
+        moveView(v, x, y, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+
+    protected Button newButton(String text, float angle)
+    {
+        Button b = new RotateButton(this,angle);
+        b.setText(text);
+        mRootLayout.addView(b);
+        b.setBackgroundResource(R.drawable.uibutton);
+        b.setTextColor(getResources().getColor(R.color.dull_4));
+        b.setTextSize(18);
+        b.setOnClickListener(this);
+        return b;
+    }
+
+    protected Button newButton(String text)
+    {
+        return newButton(text, 0);
+    }
+
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_new_menu);
+        mRootLayout = (RelativeLayout) findViewById(R.id.menu_root);
+
+        Game g = null;// Game.load(this);
+        System.out.println( (g != null) + " == " + (mGame == null) );
+        if (g != null && mGame == null) {
+            mGame = g;
+            Game.setGame(mGame);
+            mGame.newSensorManager(this);
+            Toast.makeText(this, "Chargement", Toast.LENGTH_LONG).show();
+        } else if (mGame == null) {
+            mGame = new Game("game_medium", this);
+            Game.setGame(mGame);
+            Toast.makeText(this, "Nouveau Jeu", Toast.LENGTH_LONG).show();
+        } else if (mGame == null) {
+            Toast.makeText(this, "Erreur", Toast.LENGTH_LONG).show();
+            throw new Error("Le jeu n'est pas sauvegardé");
+        }
+
+        updateSize();
+        Settings.setSettings(mGame.getmSettings());
+
+        setUpMenuStyle();
+        refreshMenuStyle();
+
+        mCustomHandler.postDelayed(updateTimerThread, 10);
+    }
+
+    public void onOptionsClick(View v)
+    {
+        Intent intent = new Intent(this, SettingsActivity.class);
+        intent.putExtra("game", mGame);
+        startActivity(intent);
+    }
+
+
+    public void onCarteClick(View v)
+    {
+        Intent intent = AndroidResources.getMapIntent(this, mGame);
+        startActivity(intent);
+    }
+
+
+    public void onScannerClick(View v)
+    {
+        Intent intent = new Intent(this, ARActivity.class);
+        intent.putExtra("game", mGame);
+        startActivity(intent);
+    }
+
+
+    public void onInventaireClick(View v)
+    {
+        Intent intent = new Intent(this, ResourceListActivity.class);
+        intent.putExtra("game", mGame);
+        startActivity(intent);
+    }
+
+    public void onCredits(View v)
+    {
+        Intent intent = new Intent(this, CreditsActivity.class);
+        startActivity(intent);
+    }
+
+
+    public void onInfoClick(View v) {
+        Stage s = mGame.getCurrentStageObj();
+        Resource r = (s!=null)?s.getResource(): null;
+
+        Intent intent = AndroidResources.getViewerIntent(this, r);
+        startActivity(intent);
+    }
+
+
+    public void onSendClick(View v)
+    {
+        Stage s = mGame.getCurrentStageObj();
+        boolean finished = mGame.nextStage();
+
+        Resource stageRes = s.getResource();
+        System.out.println("Ressource: '"+s.toString());
+        if(finished)
+        {
+            Intent intent2 = new Intent(this, EndActivity.class);
+            startActivity(intent2);
+        }
+
+        if(!s.getResourceName().isEmpty() && s.isTransition())
+        {
+            Intent intent = AndroidResources.getViewerIntent(this, stageRes);
+            startActivity(intent);
+        }
+
+        if(s.isTransition())
+        {
+            Intent intent2 = new Intent(this, TransferActivity.class);
+            startActivity(intent2);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if(view == mBBriefing) onInfoClick(view);
+        else if(view == mIbScanner) onScannerClick(view);
+        else if(view == mBMap) onCarteClick(view);
+        else if(view == mBSend) onSendClick(view);
+        else if(view == mBInvotory) onInventaireClick(view);
+        else if(view == mBOptions) onOptionsClick(view);
+    }
+
+    public void onResume()
+    {
+        super.onResume();
+        mGame = Game.game();
+        refreshMenuStyle();
+    }
+
+    protected void sendVisibility()
+    {
+        if(mGame.canSendData()) mBSend.setVisibility(View.VISIBLE);
+        else mBSend.setVisibility(View.INVISIBLE);
+    }
+
+    private Runnable updateTimerThread = new Runnable() {
+
+        public void run() {
+
+            mCustomHandler.postDelayed(this, 10);
+        }
+
+    };
+
+    public static int dpToPx(int dp)
+    {
+        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+    }
+
+    public static int pxToDp(int px)
+    {
+        return (int) (px / Resources.getSystem().getDisplayMetrics().density);
+    }
+
+
+
+}

+ 165 - 0
app/src/main/java/app/mar/activities/ModelViewerActivity.java

@@ -0,0 +1,165 @@
+package app.mar.activities;
+
+import android.content.Intent;
+import android.support.v4.view.MotionEventCompat;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import app.mar.utils.AndroidResources;
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Resource;
+import app.mar.utils.geometry.Point;
+import min3d.core.Object3d;
+import min3d.core.RendererActivity;
+import min3d.vos.CameraVo;
+import min3d.vos.Light;
+import min3d.vos.LightType;
+
+public class ModelViewerActivity extends RendererActivity  implements View.OnTouchListener, IViewerActivity {
+
+    private Resource mResource;
+    private Button mInfo;
+    private TextView mTitle;
+    private FrameLayout mFrameLayout;
+
+    protected float mPreviousX;
+    protected float mPreviousY;
+    protected float mPreviousDist=0;
+    protected CameraVo mCamera = new CameraVo();
+    protected boolean mIsZooming = false;
+    protected boolean mIsMoving = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_model_viewer);
+
+        mInfo = (Button) findViewById(R.id.info);
+        mFrameLayout = (FrameLayout) findViewById(R.id.frame_viewer);
+        mTitle = (TextView) findViewById(R.id.title);
+
+        if(getIntent().hasExtra("resource"))
+        {
+            mResource = (Resource) getIntent().getSerializableExtra("resource");
+            if(mResource!=null) setTitle(mResource.getTitle());
+            mFrameLayout.addView(_glSurfaceView);
+            mTitle.setText(mResource.getTitle());
+        }else
+        {
+            mTitle.setText("Error: Res not found");
+        }
+        FontChangeCrawler.setFont(this);
+
+
+    }
+
+    public void onClickInfo(View v)
+    {
+        Intent intent = AndroidResources.getInfoIntent(this, mResource);
+        startActivity(intent);
+    }
+
+    public void initScene()
+    {
+
+        scene.backgroundColor().setAll(0x00000000);
+
+        Light myLight = new Light();
+        myLight.position.setZ(0);
+        myLight.position.setY(0);
+        myLight.type(LightType.POSITIONAL);
+
+        scene.lights().add(myLight);
+        Object3d oo = mResource.get3DModel(this);
+        oo.position().z=mResource.getPosition().z;
+        oo.position().y=mResource.getPosition().y;
+        oo.position().x=mResource.getPosition().x;
+        oo.position().z-=20;
+        mCamera.frustum.zFar(0.1f);
+        mCamera.frustum.zFar(1000);
+        scene.addChild(mResource.get3DModel(this));
+    }
+
+    @Override
+    public void updateScene() {
+
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        float x = motionEvent.getX();
+        float y = motionEvent.getY();
+        final int action = motionEvent.getAction()%256;
+        int index = MotionEventCompat.getActionIndex(motionEvent);
+
+        if (motionEvent.getPointerCount() > 1) {
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                {
+                    if(!mIsZooming) break;
+                    Point a = new Point(motionEvent.getX(0), motionEvent.getY(0));
+                    Point b = new Point(motionEvent.getX(1), motionEvent.getY(1));
+                    double d = a.getDistanceWith(b);
+                    double delta = d - mPreviousDist;
+                    mCamera.position.z+=delta/2;
+                    mPreviousDist = (float)a.getDistanceWith(b);
+                    scene.camera(mCamera);
+                    break;
+                }
+                case MotionEvent.ACTION_POINTER_DOWN:
+                case MotionEvent.ACTION_DOWN: {
+                    Point a = new Point(motionEvent.getX(0), motionEvent.getY(0));
+                    Point b = new Point(motionEvent.getX(1), motionEvent.getY(1));
+                    mPreviousDist = (float)a.getDistanceWith(b);
+                    mIsZooming=true;
+                    break;
+                }
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_POINTER_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mIsZooming=false;
+                    mIsMoving=false;
+                    break;
+            }
+
+
+        }
+        else {
+            mIsZooming=false;
+            switch (motionEvent.getAction()) {
+                case MotionEvent.ACTION_MOVE:
+                {
+                    if(!mIsMoving) break;
+                    float dx = x - mPreviousX;
+                    float dy = y - mPreviousY;
+                    Object3d oo = mResource.get3DModel(this);
+
+                    oo.rotation().y += dx / 3;
+                    oo.rotation().x += dy / 3;
+                    _glSurfaceView.requestRender();
+
+
+                    break;
+                }
+                case MotionEvent.ACTION_DOWN:
+                    mIsMoving=true;
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mIsMoving=false;
+                    mIsZooming=false;
+                    break;
+            }
+            mPreviousX = x;
+            mPreviousY = y;
+        }
+
+
+        return true;
+
+    }
+}

+ 107 - 0
app/src/main/java/app/mar/activities/PermissionActivity.java

@@ -0,0 +1,107 @@
+package app.mar.activities;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.text.Html;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import app.mar.utils.files.FileManager;
+
+public class PermissionActivity extends AppCompatActivity {
+    private int m_nPerm=3;
+    static final int PERM_FINE_LOCATION=1337;
+    static final int ALL_PERMISSIONS=1337;
+    static final int PERM_CAMERA=1338;
+    static final int PERM_COARSE_LOCATION=1339;
+    protected TextView mText;
+
+    private static final String DISCLAMER="<h1><font color=\"#00ccff\">Attention :</font></h1> \n" +
+            "<h2><font color=\"#0099cc\">Ce jeu nécessite des déplacement en ville.</font></h2>\n" +
+            "<font color=\"#0099cc\">Veillez à respecter les règles suivantes : <br>\n" +
+            "Les enfants doivent être accompagnés d’un adulte. <br>\n" +
+            "Faites attention à la circulation. <br>\n" +
+            "Soyez vigilant sur l’environnement (altitude, marches, etc). <br>\n" +
+            "Ne jouer pas en conduisant. <br></font>\n" +
+            "\n" +
+            "<h3><font color=\"#00ccff\">Restez toujours conscient de votre environnement.</font></h3>\n" +
+            "<h3><font color=\"#00ccff\">En continuant, vous convenez que l’utilisation du jeu est à vos propres risques et responsabilités.</font></h3>";
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_permission);
+        ArrayList<String> al = new ArrayList<String>();
+
+
+        FileManager.init(this);
+        mText = (TextView) findViewById(R.id.disclamer);
+        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N) {
+            mText.setText(Html.fromHtml(DISCLAMER));
+        }else
+        {
+            mText.setText(Html.fromHtml(DISCLAMER, Html.FROM_HTML_MODE_COMPACT));
+        }
+
+
+        if (ContextCompat.checkSelfPermission(this,   Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            al.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        } else m_nPerm--;
+
+        if (ContextCompat.checkSelfPermission(this,   Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            al.add(Manifest.permission.CAMERA);
+        } else m_nPerm--;
+
+
+        if (ContextCompat.checkSelfPermission(this,   Manifest.permission.ACCESS_COARSE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            al.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+        } else m_nPerm--;
+        try {
+            if (al.size() > 0)
+                ActivityCompat.requestPermissions(this, al.toArray(new String[m_nPerm]), ALL_PERMISSIONS);
+        }catch(Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+
+    public void onRequestPermissionsResult(int requestCode,
+                                           String permissions[], int[] grantResults) {
+        for(int i=0; i<permissions.length; i++) {
+            switch (requestCode) {
+                case ALL_PERMISSIONS:
+                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
+                        m_nPerm--;
+                    else
+                    {
+                        finish();
+                        System.exit(0);
+                    }
+                    break;
+            }
+        }
+
+
+    }
+
+    public void onUnderstood(View v)
+    {
+        if(m_nPerm==0)
+        {
+            Intent intent = new Intent(this, MARMenuActivity.class);
+            startActivity(intent);
+        }
+    }
+
+}

+ 163 - 0
app/src/main/java/app/mar/activities/PuzzleActivity.java

@@ -0,0 +1,163 @@
+package app.mar.activities;
+
+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 PuzzleActivity 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_puzzle);
+
+        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);
+    }
+}

+ 93 - 0
app/src/main/java/app/mar/activities/ResourceListActivity.java

@@ -0,0 +1,93 @@
+package app.mar.activities;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
+import app.mar.ui.*;
+import app.mar.utils.*;
+import app.mar.utils.game.*;
+/**
+ * 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 ResourceListActivity extends Activity {
+    protected Game mGame;
+    protected ListView mList;
+    /*
+    protected void addStage(ArrayList<Object> ar, int stage)
+    {
+        LinearLayout nl = new LinearLayout(this);
+        CustomListView lv = new CustomListView(this);
+        ScrollView sv = new ScrollView(this);
+        TextView b = new TextView(this);
+        ResourceArrayAdapter aa;
+        b.setText("Etape "+stage);
+        b.setTextColor(Color.rgb(0x33, 0xb5, 0xe5));
+        b.setTextSize(30);
+        b.setTypeface(null, Typeface.BOLD);
+        aa = new ResourceArrayAdapter(this, android.R.layout.simple_list_item_1, ar);
+        lv.setOnItemClickListener(aa);
+        lv.setAdapter(aa);
+        nl.setOrientation(LinearLayout.VERTICAL);
+        nl.addView(b);
+        nl.addView(sv);
+        sv.addView(lv);
+        //lv.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,  LinearLayout.LayoutParams.MATCH_PARENT));
+        mLayout.addView(nl);
+    }
+    protected void fill()
+    {
+        if(mGame!=null)
+        {
+            ArrayList< ArrayList<Resource> > resources = mGame.getResourceByStage();
+            for(int i = 0; i< resources.size(); i++)
+                addStage(resources.get(i), i);
+        }
+    }*/
+    protected void fill()
+    {
+        if(mGame!=null)
+        {
+            ArrayList< ArrayList<Resource> > resources = mGame.getResourceByStage();
+            ArrayList<Object> obs = new ArrayList<Object>();
+            for(int i = 0; i< resources.size(); i++) {
+                obs.add("Étape "+i);
+                for(int j=0; j<resources.get(i).size(); j++) {
+                    obs.add(resources.get(i).get(j));
+                }
+            }
+            ResourceArrayAdapter aa;
+            aa = new ResourceArrayAdapter(this, android.R.layout.simple_list_item_1, obs);
+            mList.setOnItemClickListener(aa);
+            mList.setAdapter(aa);
+        }
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_resource_list);
+        FontChangeCrawler.setFont(this);
+        mGame = (Game)getIntent().getSerializableExtra("game");
+        mList = (ListView) findViewById(R.id.list_layout);
+        fill();
+    }
+}

+ 90 - 0
app/src/main/java/app/mar/activities/SEMenuActivity.java

@@ -0,0 +1,90 @@
+package app.mar.activities;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Created by ptitcois on 26/06/17.
+ */
+public class SEMenuActivity extends MenuActivity {
+
+    protected TextView mTVmenu;
+
+    protected  void refreshMenuStyle()
+    {
+        int size =  15;
+        int offset=mWidth/8;
+        mRootLayout.setBackgroundResource(R.drawable.background);
+        int h4 = mHeight/3-mHeight/26;
+        int w = (int)(mHeight/ 4);
+        int h = dpToPx((int)(mBBriefing.getTextSize()*1.5));
+        int H = (int)(h*1.24);
+
+        moveView(mBBriefing, mWidth/8, offset+h4,          w*2,h);
+        moveView(mBInvotory, mWidth/8, offset+h4+H,        w*2,h);
+        moveView(mBMap, mWidth/8, offset+h4+2*H,           w*2,h);
+        moveView(mBOptions, mWidth/8, offset+h4+3*H,       w*2,h);
+        moveView(mIbScanner, mWidth/8, offset+h4+4*H,      w*2, h);
+        moveView(mBSend, mWidth/8, offset+h4+5*H,          w*2, h);
+        ((TextView)mBMenu).setTextSize(25);
+        moveView(mBMenu, mWidth/10, 0, mWidth, mHeight/3);
+        moveView(mTVmenu, mWidth/7, h4);
+
+        mBBriefing.setTextSize(size);
+        mBInvotory.setTextSize(size);
+        mBMap.setTextSize(size);
+        mBOptions.setTextSize(size);
+        mIbScanner.setTextSize(size);
+        mBSend.setTextSize(size);
+        ((TextView)mBMenu).setTextSize(30);
+        ((TextView)mBMenu).setGravity(Gravity.CENTER);
+        //mBMenu.setTypeface(null,Typeface.BOLD);
+        //sendVisibility();
+        mBSend.setVisibility(View.INVISIBLE);
+
+    }
+
+    protected void setUpMenuStyle()
+    {
+        /*mIbScanner = new Button(this);
+        //mIbScanner.setImageResource(R.drawable.scanner);
+        //mIbScanner.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        mRootLayout.addView(mIbScanner);
+        mIbScanner.setBackgroundResource(R.drawable.uibuttoncircle);
+        mIbScanner.setText("Scanner");
+        mIbScanner.setTextColor(getResources().getColor(R.color.dull_4));
+        mIbScanner.setTextSize(22);
+        mIbScanner.setOnClickListener(this);*/
+
+        //moveView(mIbScanner, mWidth / 2 - mHeight / 8, mHOffset + mHeight / 2 - mHeight / 8, mHeight / 4, mHeight / 4);
+        mTVmenu = new TextView(this);
+        mTVmenu.setText("Menu");
+        moveView(mTVmenu, mWidth/7, mHeight/7);
+        mRootLayout.addView(mTVmenu);
+        mTVmenu.setTextSize(20);
+
+        mBMenu = new TextView(this);
+        ((TextView)mBMenu).setText("Une terreur dans la ville");
+        mRootLayout.addView(((TextView)mBMenu));
+        moveView(mBMenu, mWidth/10, 0, mWidth, 600);
+        ((TextView)mBMenu).setTextSize(30);
+
+        mIbScanner = newButton("Rechercher");
+        mBBriefing = newButton("Résumé");
+        mBInvotory = newButton("Inventaire");
+        mBMap = newButton("Carte");
+        mBOptions = newButton("Options");
+        //mBMenu = newButton("Menu Principal");
+        mBSend = newButton("Envoyer les données");
+        Resources r = getResources();
+        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, r.getDisplayMetrics());
+        //moveView(mBMenu, 0, 0, mWidth, (int) px);
+
+        if(!mGame.hasResource("start"))
+            mGame.pickResource("start");
+
+    }
+}

+ 249 - 0
app/src/main/java/app/mar/activities/SettingsActivity.java

@@ -0,0 +1,249 @@
+package app.mar.activities;
+
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import app.mar.ui.CustomToggleButton;
+import app.mar.ui.SelectButton;
+import app.mar.ui.OnToggleListener;
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.Settings;
+import app.mar.utils.game.Game;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ * <p/>
+ * See <a href="http://developer.android.com/design/patterns/settings.html">
+ * Android Design: Settings</a> for design guidelines and the <a
+ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
+ * API Guide</a> for more information on developing a Settings UI.
+ */
+public class SettingsActivity extends Activity implements OnToggleListener, View.OnClickListener {
+
+    protected Game         mGame;
+    protected LinearLayout mRoot;
+    protected LinearLayout mRootDev;
+    protected CustomToggleButton mAccelerometer;
+    protected CustomToggleButton mGPS;
+    protected CustomToggleButton mPlaces;
+    protected CustomToggleButton mAreas;
+    protected CustomToggleButton mDev;
+    protected CustomToggleButton mRA;
+    protected SelectButton mSensorLatency;
+    protected View mReset;
+    protected View mCheat;
+    protected Button mOk;
+
+
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_settings);
+
+        mGame = Game.game();
+        mRootDev=new LinearLayout(this);
+        mRootDev.setOrientation(LinearLayout.VERTICAL);
+        mAccelerometer = new CustomToggleButton(this);
+        mGPS = new CustomToggleButton(this);
+        mDev = new CustomToggleButton(this);
+        mDev.setOnClickListener((OnToggleListener)this);
+        mPlaces=new CustomToggleButton(this);
+        mRA=new CustomToggleButton(this);
+        mAreas=new CustomToggleButton(this);
+        mSensorLatency = new SelectButton(this, Settings.SENSORS_LATENCIES);
+        mCheat = newButton("Avoir toutes les ressources");
+        mReset = newButton("Supprimer la sauvegarde");
+        mRoot = (LinearLayout)findViewById(R.id.root_layout);
+        mOk = (Button)findViewById(R.id.sbutok);
+        addSelection("Latence des capteurs:", mSensorLatency, mRoot);
+        addAction("", mReset, mRoot);
+        addOptionBool("Mode Réalitée augmentée", mRA, mRoot);
+        addOptionBool("Mode développeur:", mDev, mRoot);
+        mRoot.addView(mRootDev);
+        addOptionBool("GPS préprogrammé:", mGPS, mRootDev);
+        addOptionBool("Afficher les zones:", mAreas, mRootDev);
+        addOptionBool("Afficher les ressources:", mPlaces, mRootDev);
+        addAction("", mCheat, mRootDev);
+        preset();
+        FontChangeCrawler.setFont(this);
+    }
+
+    protected View newButton(String text)
+    {
+        TextView b = new TextView(this);
+        b.setText(text);
+        b.setTextColor(getResources().getColor(R.color.dull_4));
+        b.setOnClickListener(this);
+        return b;
+    }
+
+    private void reset()
+    {
+        final Activity context = this;
+        AlertDialog.Builder builder1 = new AlertDialog.Builder(this);
+        builder1.setMessage("Cette action va supprimer toute la sauvegarde. La progression dans le jeu reviendra donc a 0. Êtes-vous sûr de vouloir continuer ?");
+        builder1.setCancelable(true);
+
+        builder1.setPositiveButton(
+                "Supprimer",
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        Game.setGame(new Game("game_medium", context));
+                        mGame.pickUpOtherResources();
+                        Toast.makeText(context, "Suppression effectuée", Toast.LENGTH_SHORT);
+                        dialog.cancel();
+                    }
+                });
+
+        builder1.setNegativeButton(
+                "Ne pas supprimer",
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        dialog.cancel();
+                    }
+                });
+
+        AlertDialog alert11 = builder1.create();
+        alert11.show();
+    }
+
+    public void onClick(View v)
+    {
+        if(v==mOk)
+        {
+            setResults();
+            finish();
+        }
+        else if(v==mReset)
+        {
+            reset();
+            setResults();
+        }else if(v==mCheat)
+        {
+            mGame.pickAllResoucres();
+        }
+    }
+
+    public void addOptionBool(String name, CustomToggleButton v, LinearLayout root)
+    {
+        LinearLayout ll = new LinearLayout(this);
+        TextView tv = new TextView(this);
+        tv.setText(name);
+        tv.setTextColor(getResources().getColor(R.color.dull_4));
+
+        ll.setOrientation(LinearLayout.HORIZONTAL);
+        ll.addView(tv);
+        ll.addView(v);
+        root.addView(ll);
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        lp.setMargins(30, 30, 25, 5);
+        ll.setLayoutParams(lp);
+        lp=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        lp.setMargins(0, 0, 40 , 40);
+        tv.setLayoutParams(lp);
+        v.setLayoutParams(new LinearLayout.LayoutParams(150, 80));
+        newSeparator(root);
+    }
+
+    public void addAction(String name, View v, LinearLayout root)
+    {
+        LinearLayout ll = new LinearLayout(this);
+        ll.setOrientation(LinearLayout.HORIZONTAL);
+        ll.addView(v);
+        root.addView(ll);
+
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        lp.setMargins(30, 30, 25, 5);
+        ll.setLayoutParams(lp);
+        v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 60));
+        newSeparator(root);
+    }
+
+    public void addSelection(String name, SelectButton v, LinearLayout root)
+    {
+        LinearLayout ll = new LinearLayout(this);
+        TextView tv = new TextView(this);
+        tv.setText(name);
+        tv.setTextColor(getResources().getColor(R.color.dull_4));
+
+        ll.setOrientation(LinearLayout.VERTICAL);
+        ll.addView(tv);
+        ll.addView(v);
+        root.addView(ll);
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        lp.setMargins(30, 30, 25, 5);
+        ll.setLayoutParams(lp);
+        lp=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        lp.setMargins(90, 0, 0 , 0);
+        v.setLayoutParams(lp);
+        //v.setLayoutParams(new LinearLayout.LayoutParams(150, 60));
+        newSeparator(root);
+    }
+
+    public View newSeparator(LinearLayout root)
+    {
+        View v = new View(this);
+        v.setBackgroundColor(getResources().getColor(R.color.dull_3));
+        root.addView(v);
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 2);
+        lp.setMargins(30, 30, 0,0);
+        v.setLayoutParams(lp);
+        return v;
+    }
+
+
+    private void setResults()
+    {
+        mGame.getmSettings().setDevelopperMode(mDev.getState());
+        mGame.getmSettings().setGPSDebug(mGPS.getState());
+        mGame.getmSettings().setAreaDebug(mAreas.getState());
+        mGame.getmSettings().setARMode(mRA.getState());
+        mGame.getmSettings().setResourceDebug(mPlaces.getState());
+        mGame.getmSettings().setSensorLatency(mSensorLatency.getState());
+        Settings.setSettings(mGame.getmSettings());
+        mGame.save(this);
+        mGame.getPlayer().newSensorManager(this);
+    }
+
+    private void updateVisibility()
+    {
+        int x = mDev.getState()?View.VISIBLE:View.GONE;
+        mRootDev.setVisibility(x);
+    }
+
+    private void preset()
+    {
+        mDev.setState(mGame.getmSettings().isDevelopperMode());
+        mGPS.setState(mGame.getmSettings().isGPSDebug());
+        mAreas.setState(mGame.getmSettings().isAreaDebug());
+        mPlaces.setState(mGame.getmSettings().isResourceDebug());
+        mRA.setState(mGame.getmSettings().isARMode());
+        mSensorLatency.setState(mGame.getmSettings().getSensorLatency());
+        updateVisibility();
+    }
+
+    @Override
+    public void onToggle(View v) {
+        if(v==mDev) updateVisibility();
+    }
+
+    @Override
+    public void onOn(View v) {}
+
+    @Override
+    public void onOff(View v) {}
+
+
+}

+ 81 - 0
app/src/main/java/app/mar/activities/TransferActivity.java

@@ -0,0 +1,81 @@
+package app.mar.activities;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.os.Handler;
+import android.support.v7.app.AlertDialog;
+import android.os.Bundle;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Game;
+
+public class TransferActivity extends Activity {
+
+    private Handler mCustomHandler = new Handler();
+    private ProgressBar mBar;
+    private ImageButton mImage;
+    private int mCurrentPos = 0;
+    private final int MAX_POS=210;
+    private final int PERIODE_SEND=35;
+    private Activity mSelf;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_transfer);
+        FontChangeCrawler.setFont(this);
+        mBar = (ProgressBar) findViewById(R.id.pb_bar);
+        mImage = (ImageButton) findViewById(R.id.ib_logo);
+        mBar.setMax(MAX_POS);
+        mBar.getProgressDrawable().setColorFilter(
+                Color.BLUE, android.graphics.PorterDuff.Mode.SRC_IN);
+        mSelf=this;
+        mCustomHandler.postDelayed(updateTimerThread, 10);
+    }
+
+    private Runnable updateTimerThread = new Runnable() {
+
+        public void run() {
+
+            mBar.setProgress(mCurrentPos++);
+            mImage.setAlpha(alpha());
+            if(mCurrentPos < MAX_POS) mCustomHandler.postDelayed(this, 10);
+            else
+            {
+                AlertDialog.Builder builder = new AlertDialog.Builder(mSelf);
+
+                builder.setMessage("Données, envoyées, le professeur à envoyer un message")
+                        .setTitle("Données, envoyée");
+                builder.setPositiveButton("Voir", new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        if(Game.game().getCurrentStage()-1==Game.game().getNStages())
+                        {
+                            Game.game().finish();
+                            Toast.makeText(mSelf, "Finished !!!!!!!", Toast.LENGTH_LONG).show();
+                        }
+                        finish();
+                    }
+                });
+
+                AlertDialog dialog = builder.create();
+                try{
+                    dialog.show();
+                }catch(Exception e) {
+                }
+            }
+        }
+
+    };
+
+
+    private float alpha()
+    {
+        int ix = mCurrentPos % PERIODE_SEND;
+        float x= ((float)mCurrentPos)/ ((float)PERIODE_SEND);
+        return (float)(Math.cos(x)*0.4+0.6);
+    }
+}

+ 85 - 0
app/src/main/java/app/mar/ui/CameraPreview.java

@@ -0,0 +1,85 @@
+package app.mar.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;
+
+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());
+        }
+
+    }
+}
+

+ 31 - 0
app/src/main/java/app/mar/ui/CustomListView.java

@@ -0,0 +1,31 @@
+package app.mar.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * Created by ptitcois on 31/10/16.
+ */
+
+public class CustomListView  extends ListView {
+
+    public CustomListView  (Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CustomListView  (Context context) {
+        super(context);
+    }
+
+    public CustomListView  (Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
+                MeasureSpec.AT_MOST);
+        super.onMeasure(widthMeasureSpec, expandSpec);
+    }
+}

+ 103 - 0
app/src/main/java/app/mar/ui/CustomToggleButton.java

@@ -0,0 +1,103 @@
+package app.mar.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+
+import app.mar.activities.R;
+
+/**
+ * Created by ptitcois on 31/10/16.
+ */
+public class CustomToggleButton extends Button implements View.OnClickListener {
+
+    protected boolean mState=false;
+    protected OnToggleListener mListener = null;
+
+
+    public CustomToggleButton(Context context, boolean state) {
+        super(context);
+        mState=state;
+        init();
+    }
+
+    public CustomToggleButton(Context context) {
+        super(context);
+        init();
+    }
+
+    public CustomToggleButton(Context context, boolean state, OnToggleListener l) {
+        super(context);
+        mState=state;
+        mListener=l;
+        init();
+
+    }
+
+    public CustomToggleButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public CustomToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    protected void init()
+    {
+        super.setOnClickListener(this);
+        setHeight(50);
+        update();
+    }
+
+    protected void update()
+    {
+        if(mState) onOn();
+        else onOff();
+    }
+
+    protected void onOn()
+    {
+        setText("On");
+        setTextColor(getResources().getColor(R.color.dull_4));
+        setBackgroundResource(R.drawable.togglebutton_on);
+        setHeight(50);
+        if(mListener!=null) mListener.onOn(this);
+    }
+
+    protected void onOff()
+    {
+        setText("Off");
+        setTextColor(getResources().getColor(R.color.dull_4));
+        setBackgroundResource(R.drawable.togglebutton_off);
+        setHeight(50);
+
+        if(mListener!=null) mListener.onOff(this);
+    }
+
+    public boolean getState()
+    {
+        return mState;
+    }
+
+    public void setState(boolean s)
+    {
+        mState=s;
+        update();
+    }
+
+
+    public void setOnClickListener(OnToggleListener l)
+    {
+        mListener=l;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mState=!mState;
+        update();
+        if(mListener!=null) mListener.onToggle(this);
+    }
+}

+ 156 - 0
app/src/main/java/app/mar/ui/MediaView.java

@@ -0,0 +1,156 @@
+package app.mar.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import java.io.IOException;
+
+import app.mar.utils.files.FileManager;
+
+/**
+ * Created by ptitcois on 27/03/17.
+ */
+public class MediaView extends SurfaceView implements View.OnClickListener,SurfaceHolder.Callback {
+    private MediaPlayer mPlayer;
+    private MediaView mThis;
+    private Context mContext;
+    private OnStartListener mOnStart;
+
+    public interface OnStartListener
+    {
+        public void onStart(MediaView mv);
+    }
+
+
+
+    public MediaView(Activity cont) {
+        super(cont);
+        setOnClickListener(this);
+        mThis=this;
+        mContext=cont;
+    }
+
+    public void surfaceCreated(SurfaceHolder holder)
+    {
+        if(mPlayer!=null)mPlayer.setDisplay(getHolder());
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+        if(mPlayer!=null)mPlayer.setDisplay(getHolder());
+
+    }
+
+
+    public void stop() {
+        if(mPlayer!=null && mPlayer.isPlaying())
+            mPlayer.stop();
+    }
+
+    public void updateHolder() {
+        //if(mPlayer!=null)mPlayer.setDisplay(getHolder());
+
+    }
+
+    public void clearPlayer() {
+        mPlayer=null;
+
+    }
+
+    public void play(String path)
+    {
+        final MediaView x=this;
+        AssetFileDescriptor afd;
+        mPlayer = new MediaPlayer();
+        Log.e("MediaPlayer", "Play: '"+path+"'");
+        afd = FileManager.getAfdMedia(mContext, path);
+        try {
+            mPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(), afd.getLength());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        mPlayer.prepareAsync();
+        mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                //mp.setDisplay(mThis.getHolder());
+                mp.start();
+                if(mOnStart!=null)
+                    mOnStart.onStart(x);
+            }
+        });
+
+    }
+    @Override
+    public void onClick(View view) {
+        if(mPlayer!=null)
+        {
+            if(mPlayer.isPlaying())
+            {
+                mPlayer.pause();
+            }else{
+                mPlayer.start();
+            }
+
+        }
+    }
+
+    public boolean isPlaying()
+    {
+        return mPlayer!=null && mPlayer.isPlaying();
+    }
+
+    public void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener o)
+    {
+        mPlayer.setOnSeekCompleteListener(o);
+    }
+
+    public int getDuration()
+    {
+        return mPlayer.getDuration();
+    }
+
+
+    public int getTime()
+    {
+        return mPlayer.getCurrentPosition();
+    }
+
+    public void start()
+    {
+        mPlayer.start();
+    }
+
+    public void pause()
+    {
+        mPlayer.pause();
+    }
+
+    public void seek(int x)
+    {
+        mPlayer.seekTo(x);
+    }
+
+    public void surfaceDestroyed(SurfaceHolder var1) {
+        synchronized (this) {
+            //mHasActiveHolder = false;
+            if(mPlayer!=null) mPlayer.stop();
+            mPlayer=null;
+            synchronized(this)          {
+                this.notifyAll();
+            }
+        }
+    }
+
+    public void setOnStartListener(OnStartListener mv)
+    {
+        mOnStart=mv;
+    }
+}

+ 12 - 0
app/src/main/java/app/mar/ui/OnToggleListener.java

@@ -0,0 +1,12 @@
+package app.mar.ui;
+
+import android.view.View;
+
+/**
+ * Created by ptitcois on 31/10/16.
+ */
+public interface OnToggleListener {
+    public void onToggle(View v);
+    public void onOn(View v);
+    public void onOff(View v);
+}

+ 169 - 0
app/src/main/java/app/mar/ui/ResourceArrayAdapter.java

@@ -0,0 +1,169 @@
+package app.mar.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+import app.mar.utils.AndroidResources;
+import app.mar.utils.game.Resource;
+
+
+/**
+ * Created by ptitcois on 22/08/16.
+ */
+public class ResourceArrayAdapter extends ArrayAdapter<Object> implements  AdapterView.OnItemClickListener{
+    protected Context mContext;
+    protected List<Object> mList;
+    public ResourceArrayAdapter(Context context, int resource, List<Object> objects) {
+        super(context, resource, objects);
+        mContext=context;
+        mList=objects;
+    }
+
+    @Override
+    public View getView(int position, View convertView,
+                        ViewGroup parent) {
+        View view =super.getView(position, convertView, parent);
+
+        TextView textView=(TextView) view.findViewById(android.R.id.text1);
+
+            /*YOUR CHOICE OF COLOR*/
+        textView.setTextColor(Color.rgb(0x33, 0xb5, 0xe5));
+
+        if(mList.get(position) instanceof String){
+            textView.setTextSize(30);
+            textView.setTypeface(null, Typeface.BOLD);
+        }else{
+            textView.setTextSize(15);
+            textView.setTypeface(null, Typeface.NORMAL);
+        }
+
+
+        return view;
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+        if (adapterView.getItemAtPosition(i) instanceof Resource) {
+            Resource item = (Resource) adapterView.getItemAtPosition(i);
+
+            Intent intent = AndroidResources.getViewerIntent(mContext, item);
+            mContext.startActivity(intent);
+
+        }else if(adapterView.getItemAtPosition(i) instanceof String)
+        {
+
+        }
+    }
+}
+
+
+/*package app.mar.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+import app.mar.activities.R;
+import app.mar.utils.AndroidResources;
+import app.mar.utils.FontChangeCrawler;
+import app.mar.utils.game.Resource;
+
+//
+ // Created by ptitcois on 22/08/16.
+ //
+public class ResourceArrayAdapter extends ArrayAdapter<Object> implements  AdapterView.OnItemClickListener{
+    protected Context mContext;
+    protected List<Object> mList;
+    public ResourceArrayAdapter(Context context, int resource, List<Object> objects) {
+        super(context, R.layout.array_layout, objects);
+        mContext=context;
+        mList=objects;
+
+    }
+
+    @Override
+    public View getView(int position, View convertView,
+                        ViewGroup parent) {
+
+
+        if(convertView == null){
+            //Nous récupérons notre row_tweet via un LayoutInflater,
+            //qui va charger un layout xml dans un objet View
+            convertView = LayoutInflater.from(getContext()).inflate(R.layout.array_layout, parent, false);
+        }
+
+        TextView textView=(TextView) convertView.findViewById(R.id.textObject);
+
+        textView.setTextColor(Color.rgb(0, 0, 0));
+
+        if(mList.get(position) instanceof String){
+            textView.setTextSize(30);
+            String title  = (String) mList.get(position);
+            textView.setText(title);
+            //textView.setTypeface(null, Typeface.BOLD);
+        }else{
+            textView.setTextSize(22);
+            final Resource re  = (Resource) mList.get(position);
+            //textView.setTypeface(null, Typeface.NORMAL);
+            textView.setText(re.getTitle());
+            convertView.setOnClickListener(new View.OnClickListener(){
+
+                @Override
+                public void onClick(View view) {
+                    Intent intent = AndroidResources.getViewerIntent(mContext, re);
+                    mContext.startActivity(intent);
+                }
+            });
+
+        }
+        FontChangeCrawler.setFont(textView, mContext);
+
+
+        //nous renvoyons notre vue à l'adapter, afin qu'il l'affiche
+        //et qu'il puisse la mettre à recycler lorsqu'elle sera sortie de l'écran
+        return convertView;
+
+
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+        Log.e("__________", "OK");
+        if (adapterView.getItemAtPosition(i) instanceof Resource) {
+            Resource item = (Resource) adapterView.getItemAtPosition(i);
+            Log.e("__________", "ItemClick: "+i+" "+l+ " Ressource");
+
+            Intent intent =AndroidResources.getViewerIntent(mContext, item);
+            mContext.startActivity(intent);
+
+        }else if(adapterView.getItemAtPosition(i) instanceof String)
+        {
+            Log.e("__________", "ItemClick: "+i+" "+l+ " String");
+
+        }
+        else
+        {
+
+            Log.e("__________", "ItemClick: "+i+" "+l+ " Autre");
+        }
+    }
+}
+*/

+ 43 - 0
app/src/main/java/app/mar/ui/RotateButton.java

@@ -0,0 +1,43 @@
+package app.mar.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * Created by ptitcois on 23/10/16.
+ */
+public class RotateButton extends Button {
+    private float mAngle = 45;
+    public RotateButton(Context context) {
+        super(context);
+
+    }
+
+
+    public RotateButton(Context context, float angle) {
+        super(context);
+        mAngle=angle;
+    }
+
+    public RotateButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.save();
+        canvas.rotate(mAngle, getWidth() / 2, getHeight() / 2);
+        super.onDraw(canvas);
+        canvas.restore();
+    }
+
+    public float getAngle() {
+        return mAngle;
+    }
+
+    public void setAngle(float mAngle) {
+        this.mAngle = mAngle;
+    }
+}

+ 77 - 0
app/src/main/java/app/mar/ui/SelectButton.java

@@ -0,0 +1,77 @@
+package app.mar.ui;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import app.mar.activities.R;
+
+/**
+ * Created by ptitcois on 31/10/16.
+ */
+public class SelectButton extends LinearLayout implements View.OnClickListener {
+    protected String[] mData;
+    protected Button mLeft;
+    protected Button mRight;
+    protected TextView mText;
+    protected int mState = 0;
+    protected int mHeight=70;
+    protected void setLParams(View v, int w, int h)
+    {
+        LayoutParams l =new LayoutParams(w,h);
+        l.setMargins(0,0,0,0);
+        v.setLayoutParams(l);
+    }
+
+    public SelectButton(Context context, String[] m) {
+        super(context);
+        setOrientation(HORIZONTAL);
+        mData=m;
+        mLeft = new Button(context);
+        mLeft.setBackgroundResource(R.drawable.arrow_left);
+        mLeft.setOnClickListener(this);
+
+        mRight = new Button(context);
+        mRight.setBackgroundResource(R.drawable.arrow_right);
+        mRight.setOnClickListener(this);
+
+        mText = new TextView(context);
+        if(mData.length>0)
+            mText.setText(mData[mState]);
+
+
+        addView(mLeft);
+        addView(mText);
+        addView(mRight);
+        setLParams(mLeft, mHeight, mHeight);
+        setLParams(mRight, mHeight, mHeight);
+        setLParams(mText, 200 , mHeight);
+        mText.setTextColor(getResources().getColor(R.color.dull_4));
+
+    }
+
+    public void setState(int i)
+    {
+        if(mData.length>i)
+            mText.setText(mData[mState=i]);
+    }
+
+    public int getState()
+    {
+        return mState;
+    }
+
+    @Override
+    public void onClick(View view) {
+        if(view==mLeft)
+        {
+            mState=(mState-1<0) ? (mData.length-1) : (mState-1);
+        }else if(view==mRight)
+        {
+            mState=(mState==mData.length-1) ? 0 : (mState+1);
+        }
+        mText.setText(mData[mState]);
+    }
+}

+ 85 - 0
app/src/main/java/app/mar/utils/AndroidResources.java

@@ -0,0 +1,85 @@
+package app.mar.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import app.mar.activities.ImageViewerActivity;
+import app.mar.activities.InfoActivity;
+import app.mar.activities.MediaViewerAcitvity;
+import app.mar.activities.ModelViewerActivity;
+import app.mar.utils.game.Game;
+import app.mar.utils.game.Resource;
+import min3d.core.Object3d;
+import min3d.parser.IParser;
+import min3d.parser.Parser;
+
+/**
+ * Created by ptitcois on 14/03/17.
+ */
+public class AndroidResources {
+    public static Object3d getModel(Activity context, String name)
+    {
+        IParser myParser = Parser.createParser(context, Parser.Type.OBJ, context.getResources(), "app.brest.testmin3d:raw/"+name+"_obj",true);
+        myParser.parse();
+        return myParser.getParsedObject();
+    }
+
+    private static Uri _getAudioVideo(String name, String suffix)
+    {
+        String path="android.resource://app.brest.testmin3d/raw/"+name+"_"+suffix;
+        return Uri.parse(path);
+    }
+
+    public static Uri getVideo(String name)
+    {
+        return _getAudioVideo(name, "video");
+    }
+
+
+    public static Uri getAudio(String name)
+    {
+        return _getAudioVideo(name, "audio");
+    }
+
+    public static Drawable getImage()
+    {
+        return null;
+    }
+
+
+    public static Intent getViewerIntent(Context c, Resource re)
+    {
+        Intent intent =null ;
+        System.err.println("Ici !!!!!!!!!!");
+        if(re.isAudio() || re.isVideo())
+            intent = new Intent(c, MediaViewerAcitvity.class);
+        else if(re.is3D())
+            intent= new Intent(c, ModelViewerActivity.class);
+        else if(re.isImage())
+            intent= new Intent(c, ImageViewerActivity.class);
+
+        intent.putExtra("resource", re);
+
+        return intent;
+    }
+
+    public static Intent getInfoIntent(Context c, Resource re)
+    {
+        Intent intent = new Intent(c, InfoActivity.class);
+        intent.putExtra("resource", re);
+
+        return intent;
+    }
+
+
+    public static Intent getMapIntent(Context c, Game g)
+    {
+        Intent intent = new Intent(c, ImageViewerActivity.class);
+        intent.putExtra("map", g.getName()+"_map");
+        return intent;
+    }
+    //ImageViewerActivity.class
+}

+ 97 - 0
app/src/main/java/app/mar/utils/FontChangeCrawler.java

@@ -0,0 +1,97 @@
+package app.mar.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Created by ptitcois on 06/06/17.
+ */
+public class FontChangeCrawler
+{
+    private Typeface typeface;
+
+    public FontChangeCrawler(Typeface typeface)
+    {
+        this.typeface = typeface;
+    }
+
+    public FontChangeCrawler(AssetManager assets, String assetsFontFileName)
+    {
+        typeface = Typeface.createFromAsset(assets, assetsFontFileName);
+    }
+
+    public  void replaceFonts(ViewGroup viewTree)
+    {
+        View child;
+        for(int i = 0; i < viewTree.getChildCount(); ++i)
+        {
+            child = viewTree.getChildAt(i);
+            if(child instanceof ViewGroup)
+            {
+                // recursive call
+                replaceFonts((ViewGroup)child);
+            }
+            else if(child instanceof TextView)
+            {
+                // base case
+                ((TextView) child).setTypeface(typeface);
+            }
+        }
+    }
+
+    public  void replaceFontsOnView(View child)
+    {
+        if(child instanceof TextView)
+        {
+            ((TextView) child).setTypeface(typeface);
+        }
+
+    }
+
+    public static void setFont(Activity app, String font)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), font);
+        fontChanger.replaceFonts((ViewGroup)app.findViewById(android.R.id.content));
+    }
+
+    public static void setFont(Activity app)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), "font.ttf");
+        if(fontChanger!=null) fontChanger.replaceFonts((ViewGroup)app.findViewById(android.R.id.content));
+    }
+
+    public static void setFont(ViewGroup v, Context app)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), "font.ttf");
+        fontChanger.replaceFonts(v);
+    }
+
+    public static void setFont(ViewGroup v, Context app, String font)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), font);
+        fontChanger.replaceFonts(v);
+    }
+
+
+
+    public static void setFont(View v, Context app)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), "font.ttf");
+        fontChanger.replaceFontsOnView(v);
+    }
+
+    public static void setFont(View v, Context app, String font)
+    {
+        FontChangeCrawler fontChanger = new FontChangeCrawler(app.getAssets(), font);
+        fontChanger.replaceFontsOnView(v);
+    }
+
+
+
+
+}

+ 78 - 0
app/src/main/java/app/mar/utils/GpsStub.java

@@ -0,0 +1,78 @@
+package app.mar.utils;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import app.mar.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 24/10/16.
+ */
+public class GpsStub {
+    protected ArrayList<GPSPoint> mStubs = new ArrayList<GPSPoint>();
+    protected boolean mIsActive;
+    protected int mI = 0;
+
+    public GpsStub(boolean act)
+    {
+        mIsActive=act;
+    }
+
+    public GpsStub(String r)
+    {
+        mIsActive=true;
+        add(r);
+    }
+
+
+    public void add(GPSPoint p)
+    {
+        mStubs.add(p);
+    }
+
+    public void add(String p)
+    {
+        String r[] = p.split(";");
+        for(int i=0; i<r.length; i++)
+            addLine(r[i]);
+    }
+
+    private void addLine(String str)
+    {
+        GPSPoint p =  null;
+        double lon=0, lat=0, acc=0;
+        String res[] = str.split("\\s+");
+        try {
+            if (res.length > 0) lon = (double) Double.parseDouble(res[0]);
+            if (res.length > 1) lat = (double) Double.parseDouble(res[1]);
+        }catch (Exception e)
+        {
+            Log.e("GPSStub", "Stub reading error (first) on '"+str+"'");
+        }
+
+        try {
+            if (res.length > 2) acc = (double) Double.parseDouble(res[2]);
+        }catch (Exception e)
+        {
+            Log.e("GPSStub", "Stub reading error (second) on '"+str+"'");
+        }
+
+        add(new GPSPoint(lon, lat,(float) acc));
+    }
+
+    public GPSPoint next()
+    {
+        if(mStubs.size()>0)
+        {
+            GPSPoint p = mStubs.get(mI);
+            mI++;
+            if(mI==mStubs.size())
+                mI=0;
+            return p;
+        }
+        else return null;
+    }
+
+
+}

+ 45 - 0
app/src/main/java/app/mar/utils/JSONLoader.java

@@ -0,0 +1,45 @@
+package app.mar.utils;
+
+import android.app.Activity;
+import android.util.Log;
+
+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;
+    }
+
+
+}
+

+ 249 - 0
app/src/main/java/app/mar/utils/ResourceManager.java

@@ -0,0 +1,249 @@
+package app.mar.utils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import app.mar.utils.game.Resource;
+
+/**
+ * Created by ptitcois on 18/08/16.
+ * Cette classe gère les differentes ressources
+ */
+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>();
+    protected ArrayList<Resource> m_otherResources = new ArrayList<Resource>();
+    protected Resource mLatestResource = null;
+    protected ArrayList<Resource> mResourcesPickedUp = new ArrayList<Resource>();
+    protected int mNStages;
+
+
+    public ResourceManager(ArrayList<Resource> x, int n)
+    {
+        mAllResources =x;
+        mNStages = n;
+    }
+
+    /**
+     * Constructeur par defaut.
+     * ATTENTION: doit être initialise par la fonction set(ArrayList<Resource> x, int n)
+     */
+    public ResourceManager()
+    {
+    }
+
+    /**
+     * Initialise les ressources
+     * @param x La liste des ressources
+     * @param n Le nombre d'étapes dans le jeu
+     */
+    public void set(ArrayList<Resource> x, int n)
+    {
+        mAllResources =x;
+        mNStages = n;
+    }
+
+    /**
+     * Ajout d'une ressource hors zone
+     * @param r ressource hors zone
+     */
+    public void addOtherResource(Resource r)
+    {
+        m_otherResources.add(r);
+    }
+
+    /**
+     * Récupere une ressource hors zone
+     * @return
+     */
+    public ArrayList<Resource> getOtherResource()
+    {
+        return m_otherResources;
+    }
+
+
+    /**
+     * Recupere une ressource de l'étape  courante qui nest pas encore trouvée
+     * par son nom
+     * @param name Le nom de la ressource
+     * @return Null si inexistante sinon la ressource
+     */
+    public Resource getResourceLeftByName(String name)
+    {
+        for(int i=0; i<mResourcesLeft.size(); i++)
+        {
+            if(mResourcesLeft.get(i).getName().compareTo(name)==0)
+                return mResourcesLeft.get(i);
+        }
+
+        for(int i=0; i<m_otherResources.size(); i++)
+        {
+            if(m_otherResources.get(i).getName().compareTo(name)==0)
+                return m_otherResources.get(i);
+        }
+        return null;
+    }
+
+    public void clearPickedUpResources()
+    {
+        mResourcesPickedUp.clear();
+    }
+
+    public ArrayList<Resource> getResourceLeftByName(ArrayList<String> names)
+    {
+        ArrayList<Resource> res = new ArrayList<Resource>();
+        for(int i=names.size()-1; i>=0; i--) {
+            res.add(getResourceLeftByName(names.get(i)));
+        }
+        return res;
+    }
+
+    /**
+     * Passe à l'étape N
+     * @param stage Le numero de la prochaine etape
+     */
+    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));
+    }
+
+    /**
+     * Recupere une ressource:
+     *  -Ajout dans la liste des ressources (indifferent des etapes)
+     *  -Ajout dans la liste d'inventaire (liste de liste par etape): mResourcesAquiered
+     *  -Suppression de la liste des ressource à trouver (sauf si c'est une ressource hors zone)
+     *
+     * @param name
+     * @return
+     */
+    public boolean pickUpResource(String name)
+    {
+        for(int i=0; i<mResourcesLeft.size(); i++)
+        {
+            if(mResourcesLeft.get(i).getName().compareTo(name)==0)
+            {
+                mResourcesPickedUp.add(mResourcesLeft.get(i));
+                mResourcesAquiered.get(mResourcesAquiered.size()-1).add(mResourcesLeft.get(i));
+                mLatestResource = mResourcesLeft.get(i);
+                mResourcesLeft.remove(i);
+                return true;
+            }
+        }
+
+        for(int i=0; i<m_otherResources.size(); i++)
+            if(m_otherResources.get(i).getName().compareTo(name)==0)
+            {
+                mResourcesPickedUp.add(m_otherResources.get(i));
+                if(mResourcesAquiered.size()>0)mResourcesAquiered.get(mResourcesAquiered.size()-1).add(m_otherResources.get(i));
+            }
+
+        return false;
+    }
+
+    public ArrayList<Resource> getPickedUpResources()
+    {
+        return mResourcesPickedUp;
+    }
+
+    public ArrayList<Resource> getResourceLeft()
+    {
+        return mResourcesLeft;
+    }
+
+    public ArrayList<Resource> getResourceAquiered(int stage)
+    {
+        return mResourcesAquiered.get(stage-1);
+    }
+
+
+    public ArrayList< ArrayList<Resource> > getResourceAquiered()
+    {
+        return mResourcesAquiered;
+    }
+
+    /**
+     * Renvoie le nombre de ressource acquise par le joueur depuis le debut de la partie
+     * @return
+     */
+    public int getNResourceAquired()
+    {
+        int n=0;
+        for(int i=0; i<mResourcesAquiered.size(); i++)
+            n+=mResourcesAquiered.get(i).size();
+        return n;
+    }
+
+    /**
+     * Récupre toutes les ressources en un coups
+     */
+    public void pickAllResoucres()
+    {
+        mResourcesLeft.clear();
+        mResourcesAquiered.clear();
+
+        mResourcesAquiered.add(new ArrayList<Resource>());
+
+        for(int i=0; i<m_otherResources.size(); i++)
+            mResourcesAquiered.get(0).add(m_otherResources.get(i));
+
+        for(int i=0; i<mNStages; i++)
+        {
+            ArrayList<Resource> ar = new ArrayList<Resource>();
+            for(int j=0; j<mAllResources.size(); j++) {
+                if (mAllResources.get(j).getStage() == i + 1)
+                    ar.add(mAllResources.get(j));
+            }
+            mResourcesAquiered.add(ar);
+        }
+
+
+    }
+
+    /**
+     * Enleve le Modele 3D de la cache
+     */
+    public void deleteAll3DModel()
+    {
+        for(int i=0; i<mAllResources.size(); i++)
+            mAllResources.get(i).delete3DModel();
+    }
+
+    /**
+     * Le nombre de ressources acquise dans l'étape courante
+     * @return
+     */
+    public int getNResourceAquiredByStage()
+    {
+        if(mResourcesAquiered.size()>0)
+            return mResourcesAquiered.get(mResourcesAquiered.size()-1).size();
+        return 0;
+    }
+
+
+    public int getNResourceLeft()
+    {
+        return mResourcesLeft.size();
+    }
+
+    public Resource getLatestResource()
+    {
+        return mLatestResource;
+    }
+
+
+    public boolean hasResource(String name)
+    {
+        for(int i=0; i<mResourcesAquiered.size(); i++)
+        {
+            for(int j=0; j<mResourcesAquiered.get(i).size(); j++)
+                if(mResourcesAquiered.get(i).get(j).getName().compareTo(name)==0)
+                    return true;
+        }
+        return false;
+    }
+}

+ 227 - 0
app/src/main/java/app/mar/utils/SensorsManager.java

@@ -0,0 +1,227 @@
+package app.mar.utils;
+
+import android.Manifest;
+import android.app.Activity;
+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 android.widget.Toast;
+
+import app.mar.activities.ARActivity;
+import app.mar.activities.R;
+import app.mar.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 18/08/16.
+ */
+public class SensorsManager implements SensorEventListener, LocationListener {
+
+    //public final boolean USE_GPS_STUB=true;
+    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 Activity mParent;
+    private GpsStub mStub=null;
+    private boolean mHasAcceleromter = true;
+    private boolean mHasMagneticField = true;
+
+    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 boolean useGpsStub()
+    {
+        return app.mar.utils.Settings.getSettings().isGPSDebug();
+    }
+
+    public GPSPoint getPosition()
+    {
+        if(!mIsLocalised) return null;
+        return new GPSPoint(mLongitude, mLatitude, mAccuracy);
+    }
+
+    public boolean hasMagneticField() {
+        return mHasMagneticField;
+    }
+
+    public boolean hasAcceleromter() {
+        return mHasAcceleromter;
+    }
+
+    public SensorsManager(Activity 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);
+
+        mHasMagneticField = mMagnetometer!=null;
+        mHasAcceleromter = mAccelerometer!=null;
+
+        mBufferX = new SlideBuffer(app.mar.utils.Settings.getSettings().getSensorLatency());
+        mBufferY = new SlideBuffer(app.mar.utils.Settings.getSettings().getSensorLatency());
+        mBufferZ = new SlideBuffer(app.mar.utils.Settings.getSettings().getSensorLatency());
+
+        //debug du GPS "GPS Préprogrammé"
+        if(useGpsStub()) {
+            mIsLocalised=true;
+            mStub=new GpsStub(parent.getResources().getString(R.string.gps_stub));
+            nextStub();
+        }
+    }
+
+    public void onResume() {
+        restartSensors();
+    }
+
+
+    public void restartSensors()
+    {
+        boolean a = mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
+        boolean b = 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() {
+        stopSensors();
+    }
+
+    public void stopSensors()
+    {
+        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 && event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+            String str="";
+            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;
+        if(!useGpsStub()) {
+            mLongitude = location.getLongitude();
+            mLatitude = location.getLatitude();
+            mAccuracy = location.getAccuracy();
+        }
+
+        ARActivity.setLocation(new GPSPoint(mLatitude, mLongitude, mAccuracy));
+        //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) {}
+
+    public void nextStub()
+    {
+        if(useGpsStub())
+        {
+            GPSPoint stub = mStub.next();
+            if(stub==null) return;
+            mLongitude=stub.getX();
+            mLatitude=stub.getY();
+            mAccuracy=stub.getAccuracy();
+            ARActivity.setLocation(new GPSPoint(mLatitude, mLongitude, mAccuracy));
+        }
+    }
+}

+ 86 - 0
app/src/main/java/app/mar/utils/Settings.java

@@ -0,0 +1,86 @@
+package app.mar.utils;
+
+import java.io.Serializable;
+
+/**
+ * Created by ptitcois on 22/08/16.
+ */
+public class Settings implements Serializable{
+
+    transient static Settings mSettings = new Settings();
+
+    transient public static final String[] SENSORS_LATENCIES = {"Très rapide", "Rapide", "Normale", "Longue", "Très longue"};
+    protected boolean mAccelerometerDebug = false;
+    protected boolean mGPSDebug = false;
+    protected boolean mAreaDebug = false;
+    protected boolean mResourceDebug = false;
+    protected boolean mDevelopperMode = false;
+    protected int     mSensorLatency = 2; // 0 -> 4
+    protected boolean mARMode = true;
+
+    public Settings()
+    {
+
+    }
+
+    public boolean isAccelerometerDebug() {
+        return mAccelerometerDebug;
+    }
+
+    public void setAccelerometerDebug(boolean mAccelerometerDebug) {
+        this.mAccelerometerDebug = mAccelerometerDebug;
+    }
+
+    public boolean isGPSDebug() {
+        return mDevelopperMode && mGPSDebug;
+    }
+
+    public void setGPSDebug(boolean mGPSDebug) {
+        this.mGPSDebug = mGPSDebug;
+    }
+
+    public boolean isAreaDebug() {
+        return mDevelopperMode && mAreaDebug;
+    }
+
+    public void setAreaDebug(boolean mAreaDebug) {
+        this.mAreaDebug = mAreaDebug;
+    }
+
+    public boolean isResourceDebug() {
+        return mDevelopperMode && mResourceDebug;
+    }
+
+    public void setResourceDebug(boolean mResourceDebug) {
+        this.mResourceDebug = mResourceDebug;
+    }
+
+    public boolean isDevelopperMode() {
+        return mDevelopperMode;
+    }
+
+    public void setDevelopperMode(boolean mDevelopperMode) {
+        this.mDevelopperMode = mDevelopperMode;
+    }
+
+    public int getSensorLatency() {
+        return mSensorLatency;
+    }
+
+    public void setSensorLatency(int mSensorLatency) {
+        this.mSensorLatency = mSensorLatency;
+    }
+
+    public boolean isARMode() {
+        return mARMode;
+    }
+
+    public void setARMode(boolean mARMode) {
+        this.mARMode = mARMode;
+    }
+
+    public static Settings getSettings() { return mSettings; }
+    public static void setSettings(Settings s){mSettings=s;}
+
+
+}

+ 154 - 0
app/src/main/java/app/mar/utils/SlideBuffer.java

@@ -0,0 +1,154 @@
+package app.mar.utils;
+
+/*
+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;
+    }
+
+}*/
+
+public class SlideBuffer {
+    private float mDataSin;
+    private float mDataCos;
+    private float mCoef = 0.1f;
+    private final static float[] SENSORS_COEFS = {0.4f, 0.2f, 0.1f, 0.05f, 0.025f};
+    private int mSize;
+    private int mHead;
+
+    public SlideBuffer(int latency_level)
+    {
+        mCoef=SENSORS_COEFS[latency_level];
+        mDataSin = 0;
+        mDataCos = 0;
+        mSize = 0;
+        mHead =0;
+    }
+
+    public void setLatencyLevel(int x)
+    {
+        mCoef=SENSORS_COEFS[x];
+    }
+
+    public void enqueue(float _val)
+    {
+        float val = -_val-90;
+        mDataSin= mDataSin + mCoef*((float)Math.sin(val*Math.PI/180)-mDataSin);
+        mDataCos= mDataCos + mCoef*((float)Math.cos(val*Math.PI/180)-mDataCos);
+    }
+
+    public float average()
+    {
+        return (float)( (((Math.atan2(mDataCos, mDataSin))*180/Math.PI)+180f) % 360);
+    }
+
+}
+
+/*
+public class SlideBuffer {
+    private float mDataCos[] ;
+    private float mDataSin[];
+    private int mSize;
+    private int mI=0;
+    private int mHead;
+
+    public SlideBuffer(int size)
+    {
+        mSize = size;
+        mHead =0;
+        mDataCos = new float[size];
+        mDataSin = new float[size];
+    }
+
+    public void enqueue(float val)
+    {
+        //mData= mData + 0.5f*(val-mData);
+        mDataCos[mI]=(float)Math.cos(val);
+        mDataSin[mI]=(float)Math.sin(val);
+        mI=(mI+1)%mSize;
+
+    }
+
+    public float average()
+    {
+        float avSin=0, avCos=0;
+        for(int i=0; i<mSize; i++)
+        {
+            avSin+=mDataSin[i];
+            avCos+=mDataCos[i];
+        }
+        avSin/=mSize;
+        avCos/=mSize;
+        return (float)((Math.PI+Math.atan2(avCos, avSin))*180/Math.PI);
+    }
+
+}
+*/
+
+
+/*
+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_mkv, float alpha ) {
+        if ( output_mkv == null )
+            return input;
+        for ( int i=0; i<input.length; i++ ) {
+            output_mkv[i] = output_mkv[i] + alpha * (input[i] - output_mkv[i]);
+        }
+        return output_mkv;
+    }
+
+    public float average()
+    {
+
+            float acc=0;
+            for(int i=0; i<mSize; i++)
+                acc+=mData[i];
+            return acc/mSize;
+    }
+
+
+
+}
+*/

+ 114 - 0
app/src/main/java/app/mar/utils/files/FileManager.java

@@ -0,0 +1,114 @@
+package app.mar.utils.files;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by ptitcois on 27/03/17.
+ */
+public class FileManager {
+    protected static AssetManager m_asset=null;
+    protected static Activity m_act = null;
+
+    public static void init(Activity a)
+    {
+        m_act=a;
+        m_asset=a.getAssets();
+    }
+
+    private static boolean isInit() throws Exception {
+
+        return true;
+    }
+
+    public static InputStream openFile(Context a, String path) throws Exception {
+        isInit();
+        InputStream is = null;
+        System.err.println("ICIIIIIIIIIIIIII '"+path+"'");
+        try {
+            is = a.getAssets().open(path);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return is;
+    }
+    private static final String[]  IMG_EXT={"", ".jpg" , ".JPG",
+            ".jpeg" , ".JPEG",
+            ".bmp", ".BMP",
+            ".gif", ".GIF",
+            ".png", ".PNG"
+            };
+    private static final String[]  MEDIA_EXT={"", ".3gp" , ".3GP",
+            ".mp4", ".MP4",
+            ".avi", ".AVI",
+            ".webm", ".WEBM",
+            ".mkv", ".MKV",
+            ".aac", ".AAC",
+            ".flac", ".FLAC",
+            ".mid", ".MID",
+            ".midi", ".MIDI",
+            ".mp3", ".MP3",
+            ".3gpp", ".3GPP",
+            ".wav", ".WAV",
+            ".ogg", ".OGG"};
+
+    private static InputStream testIimage(Context a,String path) throws Exception {
+        for(int i=0; i<IMG_EXT.length; i++)
+        {
+            InputStream is = openFile(a, path+IMG_EXT[i]);
+            if(is!=null)
+                return is;
+        }
+        return null;
+    }
+
+    public static Bitmap openImage(Context a,String path) throws Exception {
+        isInit();
+        InputStream is = null;
+        Bitmap bitmap = null;
+        try {
+            is = testIimage(a, path);
+            bitmap = BitmapFactory.decodeStream(is);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return bitmap;
+    }
+
+    public static Uri getUri(Context a, String path) throws Exception {
+        isInit();
+        return Uri.parse("file:///android_asset/" + path);
+    }
+
+
+    private static AssetFileDescriptor testVideo(Context a,String path) {
+        for(int i=0; i<MEDIA_EXT.length; i++)
+        {
+            AssetFileDescriptor is = null;
+            try {
+                is = a.getAssets().openFd(path+MEDIA_EXT[i]);
+
+                if(is!=null)
+                    return is;
+            } catch (IOException e) {
+                //e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    public static AssetFileDescriptor getAfdMedia(Context a, String path)
+    {
+        return testVideo(a, path);
+    }
+
+}

+ 47 - 0
app/src/main/java/app/mar/utils/files/JSONAssetsManager.java

@@ -0,0 +1,47 @@
+package app.mar.utils.files;
+
+import android.app.Activity;
+import android.util.Log;
+
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+/**
+ * Created by ptitcois on 27/03/17.
+ */
+public class JSONAssetsManager {
+        public static JSONObject load(Activity context, String name)
+        {
+            InputStream inputStream = null;
+            try {
+                inputStream = FileManager.openFile(context, name);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            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;
+        }
+
+}

+ 157 - 0
app/src/main/java/app/mar/utils/game/Area.java

@@ -0,0 +1,157 @@
+package app.mar.utils.game;
+
+import android.app.Activity;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import app.mar.utils.geometry.GPSPoint;
+import app.mar.utils.geometry.Shape;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Area  implements Serializable {
+
+    protected String            mName;
+    protected ArrayList<String>            mResourceName = new ArrayList<String>();
+    protected Shape             mShape = new Shape();
+    protected ArrayList<Place>  mPlaces = new ArrayList<Place>();
+    protected ArrayList<Resource>          mResource = new ArrayList<Resource>();
+
+    public Area(JSONObject root, Activity act)
+    {
+        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");
+            String res [] = root.getString("resource").split(";");
+            for(int i=0; i<res.length; i++) mResourceName.add(res[i]);
+            ja = root.getJSONArray("points");
+            for(int i=0; i<ja.length(); i++) {
+                mPlaces.add(new Place(ja.getJSONObject(i)));
+            }
+            for(int i=0; i<mResourceName.size(); i++)
+                mResource.add(new Resource(mResourceName.get(i), act));
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public ArrayList<String> getResourceNames() {
+        return mResourceName;
+    }
+
+    public ArrayList<Resource> getResource() {
+        return mResource;
+    }
+    public Resource getResource(int i) {
+        return mResource.get(i);
+    }
+    public int countResource() {
+        return mResource.size();
+    }
+
+
+    public int getStage()
+    {
+        if(mResource==null || mResource.size()<=0) return -1;
+        int max=0;
+        for(int i=0; i<mResource.size(); i++)
+            if(mResource.get(i).getStage()>max)
+                max=mResource.get(i).getStage();
+        return max;
+    }
+
+    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", mResourceName);
+        }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;
+    }
+
+    public String toString(Player p)
+    {
+        String str = "";
+        str+="["+mName+" "+mPlaces.get(0).getDistance(p)+"m ]";
+        return str;
+    }
+}

+ 645 - 0
app/src/main/java/app/mar/utils/game/Game.java

@@ -0,0 +1,645 @@
+package app.mar.utils.game;
+
+import android.app.Activity;
+import android.app.admin.SystemUpdatePolicy;
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+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.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+
+import app.mar.activities.ARActivity;
+import app.mar.utils.ResourceManager;
+import app.mar.utils.SensorsManager;
+import app.mar.utils.Settings;
+import app.mar.utils.files.JSONAssetsManager;
+
+/**
+ * Created by ptitcois on 19/08/16.
+ */
+public class Game  implements Serializable {
+    private static Game gGame = null;
+    protected ArrayList<Area>  mAreas = new ArrayList<Area>();
+    protected ArrayList<Area>  mCurrentStageAreas = new ArrayList<Area>();
+    protected ArrayList<Stage> mStages = new ArrayList<Stage>();
+    protected int              mNStages;
+    protected int              mCurrentStage=0;
+    protected Player           mPlayer;
+    public ResourceManager     mResources;
+    protected String           mName;
+    protected Settings         mSettings = new Settings();
+    protected boolean          mFinished= false;
+    protected boolean          mFirstLaunch=true;
+
+    protected String           mMap=null;
+
+    public Game(String name, Activity act)
+    {
+        JSONObject root = JSONAssetsManager.load(act, name);
+        mName=name;
+        mResources = new ResourceManager();
+        mPlayer = new Player(act);
+        try {
+            /* Listes des zones */
+            JSONArray areas = root.getJSONArray("areas");
+
+            /* Listes des étapes */
+            JSONArray stages = root.getJSONArray("stages_list");
+
+            /* Nom (uniquement) d'autres ressources utilisés (hors zones) */
+            JSONArray others = root.getJSONArray("others");
+
+
+            for(int i=0; i<others.length(); i++)
+                mResources.addOtherResource(new Resource(others.getString(i), act));
+
+            for(int i=0; i<areas.length(); i++)
+                mAreas.add(new Area(areas.getJSONObject(i), act));
+
+            for(int i=0; i<stages.length(); i++) {
+                Stage s = new Stage(stages.getJSONObject(i));
+                Resource r = findResource(s.getResourceName());
+                Log.e("_______", "Res = "+r+" "+s.getResourceName());
+                s.setResource(r);
+                mStages.add(s);
+            }
+            findMaxStage();
+            mResources.set( getAllResources(), mNStages);
+
+            nextStage();
+        } catch (Exception e) {
+            System.err.println("_________________ B\n");
+            e.printStackTrace();
+        }
+
+        try {
+            System.err.println("_________________ C\n");
+            mMap=root.getString("map");
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("_________________ C\n");
+        }
+
+
+        if(!mPlayer.hasMagneticField() || !mPlayer.hasAcceleromter())
+        {
+            mSettings.setARMode(false);
+            if(!mPlayer.hasMagneticField()) Toast.makeText(act, "Votre dispositif n'a pas de boussole. Le mode RA ne peut fonctionner et a été désactivé", Toast.LENGTH_LONG).show();
+            else if(!mPlayer.hasAcceleromter()) Toast.makeText(act, "Votre dispositif n'a pas d'accélerometre. Le mode RA ne peut fonctionner et a été désactivé", Toast.LENGTH_LONG).show();
+        }
+
+        System.err.println("___________||||| "+mCurrentStageAreas.size());
+    }
+
+    public Stage getCurrentStageObj()
+    {
+        return mCurrentStage<=mStages.size()?mStages.get(mCurrentStage-1):null;
+    }
+
+    public void checkFirstLaunch()
+    {
+        if(mFirstLaunch) {
+            mFirstLaunch=false;
+        }
+    }
+
+    public Stage getStage(int i)
+    {
+        return mStages.get(i);
+    }
+
+    public Settings getmSettings(){return mSettings;}
+    public void setSettings(Settings s){ mSettings=s;}
+
+    public int getNStages() {
+        return mNStages;
+    }
+    public String getName() {
+        return mName;
+    }
+
+    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++;
+        Log.e("------------", "Is Finished : "+mCurrentStage+" > "+mNStages+" = "+(mCurrentStage>mNStages));
+        if(mCurrentStage>mNStages) return true;
+    System.out.println("La");
+        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));
+        }
+        System.err.println("================================ ICI =======================" + mCurrentStageAreas.size());
+    }
+
+    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().compareTo(name)==0)
+                mCurrentStageAreas.remove(i);
+    }
+
+    private void removeArea(int index)
+    {
+        mCurrentStageAreas.remove(index);
+    }
+
+    /**
+     * Récupere les ressources pouvant être "physiquement" (positionnement et
+     * orientation) prise par le joueur.
+     * @return true si l'étape est finie, sinon false
+     */
+    public boolean pickResource()
+    {
+        mResources.clearPickedUpResources();
+        ArrayList<LocatedResources> r = getResourcesNextToPlayer();
+
+        // recupere toutes les ressource "proche" (prenables)
+        // du joueur
+        for(int i=0; i<r.size(); i++) {
+            for (int j = 0; j < mCurrentStageAreas.size(); j++) {
+                if (r.get(i).getResource(0).getName().compareTo(mCurrentStageAreas.get(j).getResourceNames().get(0)) == 0 &&
+                        r.get(i).getResource(0).getStage() == mCurrentStage) {
+                    removeArea(j);
+                }
+            }
+            for(int k=0; k<r.get(i).count(); k++)
+                pickResource(r.get(i).getResource(k).getName());
+        }
+
+        //retourne vrai si l'etape est finie
+        return mCurrentStageAreas.size()==0;
+    }
+
+    /**
+     * Retourne la liste de toutes les ressources acquises par le joueur
+     * @return la liste de toutes les ressources acquises par le joueur
+     */
+    public ArrayList<Resource> getPickedUpResources()
+    {
+        return mResources.getPickedUpResources();
+    }
+
+
+    /**
+     * Récupere une ressource par son nom, sans besoin de localisation ou d'orientation
+     * @param name Le nom de la ressource
+     * @return  true si l'étape est finie, sinon false
+     */
+    public boolean pickResource(String name, boolean otherRes)
+    {
+
+        if(otherRes)
+        {
+            for (int j = 0; j < mCurrentStageAreas.size(); j++)
+                if (name.compareTo(mCurrentStageAreas.get(j).getName())==0) {
+                    removeArea(j);
+                }
+        }
+        mResources.pickUpResource(name);
+
+        return mCurrentStageAreas.size()==0;
+    }
+
+
+    /**
+     * Récupere une ressource par son nom, sans besoin de localisation ou d'orientation
+     * @param name Le nom de la ressource
+     * @return  true si l'étape est finie, sinon false
+     */
+    public boolean pickResource(String name)
+    {
+
+        return pickResource(name, true);
+    }
+
+    /**
+     * Récupere toutes les ressources du jeu
+     */
+    public void pickAllResoucres()
+    {
+        mResources.pickAllResoucres();
+        mCurrentStage=mNStages;
+    }
+
+    /**
+     * La derniere ressource acquise
+     * @return La derniere ressource acquise
+     */
+    public Resource getLatestResource()
+    {
+        return mResources.getLatestResource();
+    }
+
+
+    public String toString()
+    {
+        String out = "Game : "+super.toString();
+        out+="mAreas[]:\n";
+        for(int i=0; i<mAreas.size(); i++)
+            out+="\tmAres["+i+"] = "+mAreas.get(i).getJson().toString()+"\n";
+        out+="\nmResourcesAcquired[] : \n";
+        for(int i=0; i<mResources.getResourceAquiered().size(); i++) {
+            out+="\tmResourcesAcquired["+i+"][] : \n";
+            for (int j = 0; j < mResources.getResourceAquiered().get(i).size(); j++)
+                out += "\t\tmResourcesAcquired[" + i + "]["+j+"] = " + mResources.getResourceAquiered().get(i).get(j).getJson().toString() + "\n";
+        }
+        out+="mNStages :" +mNStages +"\n";
+        out+="mCurrentStage" + mCurrentStage +"\n";
+        if(mMap!=null) out+="mMap : '"+mMap+"'\n";
+        else out+="mMap : null\n";
+        return out;
+    }
+
+    public String toString2()
+    {
+        String out = "Game : "+mResources.getResourceLeft()+" : "+mCurrentStageAreas+";";
+        ArrayList<Resource> arr= mResources.getResourceLeft();
+
+        return out+";";
+    }
+
+
+    /**
+     * Sauvegarde la partie (l'objet game)
+     * @param context Le contexte
+     * @return Vrai en cas de succes, faux en cas d'erreur
+     */
+    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;
+    }
+
+    /**
+     * Charge une partie
+     * @param context Le contexte
+     * @return L'objet Game en cas de succes, null en cas d'erreur
+     */
+    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;
+        }
+    }
+
+    /**
+     *
+     * @return Retourne toutes les ressources
+     */
+    public ArrayList<Resource> getAllResources()
+    {
+        ArrayList<Resource> tmp = new ArrayList<Resource>();
+        for(int i=0; i<mAreas.size(); i++)
+        {
+            ArrayList<Resource> c = mAreas.get(i).getResource();
+            for(int j=0; j<c.size(); j++)
+                if(!tmp.contains(c.get(j)))
+                    tmp.add(c.get(j));
+        }
+
+        return tmp;
+    }
+
+    public void pickUpOtherResources()
+    {
+        ArrayList<Resource> r = mResources.getOtherResource();
+        for(int i=0; i<r.size(); i++) {
+            pickResource(r.get(i).getName(), false);
+        }
+    }
+
+
+    public ArrayList<Area> getCurrentStageAreas()
+    {
+        return mCurrentStageAreas;
+    }
+
+    /**
+     * Teste si le jeu est déja sauvé
+     * @param context Le contexte
+     * @return Vrai ou faux
+     */
+    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);
+    }
+
+    /**
+     * Permet de recuperer une ressource par son nom
+     * @param name Le nom de la ressource
+     * @return La ressource ou null (en cas d'erre)
+     */
+    public Resource findResource(String name)
+    {
+        for(int i=0; i<mAreas.size(); i++)
+            for(int j=0; j<mAreas.get(i).countResource(); j++)
+                if(mAreas.get(i).getResourceNames().get(j).compareTo(name)==0)
+                 return mAreas.get(i).getResource(j);
+        if(mResources !=null)
+            for(int i=0; i<mResources.getOtherResource().size(); i++)
+                if(mResources.getOtherResource().get(i).getName().compareTo(name)==0)
+                    return mResources.getOtherResource().get(i);
+        return null;
+    }
+
+    /**
+     * Charge la ressource 3d quand on entre dans une zone
+     * @param contecxt Contexte
+     * @return Vrai en cas de succes, faux pour une erreur
+     */
+    synchronized 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;
+                ArrayList<Resource> rs = mResources.getResourceLeftByName(mAreas.get(i).getResourceNames());
+                try {
+                    for (int j = 0; j < rs.size(); j++) {
+                        if(rs.get(j)!=null) rs.get(j).get3DModel(contecxt);
+                    }
+
+                }catch(Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return b;
+    }
+
+
+    /**
+     *
+     * Fonction principale de positionnnemnt et orientation:
+     * renvoie la liste des zones sur lesquelles le joueur est présent
+     * @return la liste des zones sur lesquelles le joueur est présent
+     */
+    public ArrayList<Area> getAreaNextToPlayer()
+    {
+        ArrayList<Area> r = new ArrayList<Area>();
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            if(mCurrentStageAreas.get(i).isOnArea(mPlayer))
+                r.add(mCurrentStageAreas.get(i));
+
+        }
+        return r;
+    }
+
+
+    /**
+     *
+     * renvoie la liste des ressources sur lesquelles le joueur est présent
+     * @return la liste des ressources sur lesquelles le joueur est présent
+     */
+    public ArrayList<LocatedResources> getResourcesNextToPlayer()
+    {
+        ArrayList<LocatedResources> r = new ArrayList<LocatedResources>();
+
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            boolean b = mCurrentStageAreas.get(i).isOnArea(mPlayer);
+
+            if(b)
+            {
+
+                if(i<mCurrentStageAreas.size() /*&& mCurrentStageAreas.get(i).isOnDirection(mPlayer)*/ &&
+                        mCurrentStageAreas.get(i).isOnPlace(mPlayer))
+                {
+                    ArrayList<Resource> rs = mResources.getResourceLeftByName(mCurrentStageAreas.get(i).getResourceNames());
+                    r.add(new LocatedResources(
+                            rs,
+                            mCurrentStageAreas.get(i).getPlace(0)
+                    ));
+                }
+            }
+        }
+        return r;
+    }
+
+
+    /**
+     * Renvoie dans une chaine la distance entre le joueur et la ressource la plus proche
+     * @return La distance en metres
+     */
+    public String getResourcesNextToPlayerDist()
+    {
+        double min=1000000000;
+        ArrayList<Resource> r = new ArrayList<Resource>();
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            if(mCurrentStageAreas.get(i).isOnArea(mPlayer))
+            {
+                if(mCurrentStageAreas.get(i).getDistanceToNextPlace(mPlayer)<min)
+                    min=mCurrentStageAreas.get(i).getDistanceToNextPlace(mPlayer);
+
+            }
+        }
+        if(min>10000000) return " --.- m";
+        NumberFormat formatter = new DecimalFormat("#0.0");
+        return formatter.format(min)+" m";
+    }
+
+    /**
+     * Renvoie la distance entre le joueur et la ressource la plus proche
+     * @return La distance en metres
+     */
+    public double getResourcesNextToPlayerDistDouble()
+    {
+        double min=1000000000;
+        ArrayList<Resource> r = new ArrayList<Resource>();
+        for(int i=0; i<mCurrentStageAreas.size(); i++)
+        {
+            //probleme de GPS..
+            if(mCurrentStageAreas.get(i).isOnArea(mPlayer))
+            {
+                if(mCurrentStageAreas.get(i).getDistanceToNextPlace(mPlayer)<min)
+                    min=mCurrentStageAreas.get(i).getDistanceToNextPlace(mPlayer);
+
+            }
+        }
+        if(min>10000000) return -1;
+        return min;
+
+    }
+
+
+
+    public Player getPlayer()
+    {
+        return mPlayer;
+    }
+
+
+    public void removeCached3DModels()
+    {
+        mResources.deleteAll3DModel();
+    }
+
+    public void newSensorManager(Activity act)
+    {
+        mPlayer.newSensorManager(act);
+        if(!mPlayer.hasMagneticField() || !mPlayer.hasAcceleromter())
+            mSettings.setARMode(false);
+    }
+
+    public ArrayList< ArrayList<Resource> > getResourceByStage()
+    {
+        return mResources.getResourceAquiered();
+    }
+
+    public void restartSensors()
+    {
+        mPlayer.getSensorsManager().restartSensors();
+    }
+
+    public void stopSensors()
+    {
+        mPlayer.getSensorsManager().stopSensors();
+    }
+    public void printNResource(String str)
+    {
+    }
+
+
+    public static void setGame(Game m)
+    {
+        gGame=m;
+    }
+
+    public static Game game()
+    {
+        return gGame;
+    }
+
+
+    public boolean canSendData() {
+        Stage s =getCurrentStageObj();
+        if(s==null ) return false;
+        if(!s.isTransition()) return false;
+        return mResources.getNResourceAquiredByStage()>=s.getNResourceStep() && !isFinished();
+        //return mResources.getNResourceLeft()<s.getNResourceStep() && !isFinished();
+    }
+
+    public boolean isFinished() { return mFinished; }
+
+    public void finish() { mFinished=true; }
+
+    public int getNResourcesAquired()
+    {
+        return mResources.getNResourceAquired();
+    }
+
+    public boolean hasResource(String name)
+    {
+        return mResources.hasResource(name);
+    }
+
+
+    public void printStage()
+    {
+        Log.e("---------------", "Stage:"+getCurrentStage());
+        for(int i=0; i<mResources.getResourceLeft().size(); i++) {
+            Log.e("---------------", "   '" + mResources.getResourceLeft().get(i).getName() + "'");
+
+        }
+    }
+
+}

+ 42 - 0
app/src/main/java/app/mar/utils/game/LocatedResources.java

@@ -0,0 +1,42 @@
+package app.mar.utils.game;
+
+import java.util.ArrayList;
+
+/**
+ * Created by ptitcois on 21/10/16.
+ */
+public class LocatedResources {
+    private Place mPlace = null;
+    private ArrayList<Resource> mResources = new ArrayList<Resource>();
+    public LocatedResources(ArrayList<Resource> r, Place p) { mPlace=p; mResources=r;}
+    public LocatedResources(ArrayList<Resource> r) {mResources=r;}
+    public LocatedResources(Place p) { mPlace=p;}
+    public LocatedResources() {}
+
+    public Place getPlace() {
+        return mPlace;
+    }
+
+    public void setPlace(Place mPlace) {
+        this.mPlace = mPlace;
+    }
+
+    public ArrayList<Resource> getResources() {
+        return mResources;
+    }
+
+    public Resource getResource(int i) {
+        return mResources.get(i);
+    }
+
+    public int count() {
+        return mResources.size();
+    }
+
+    public void setResource(ArrayList<Resource> mResource) {
+        this.mResources = mResource;
+    }
+    public void addResource(Resource mResource) {
+        this.mResources.add(mResource);
+    }
+}

+ 131 - 0
app/src/main/java/app/mar/utils/game/Place.java

@@ -0,0 +1,131 @@
+package app.mar.utils.game;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import app.mar.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 = root.getDouble("radius");
+            mField = root.getDouble("field");
+            mAngle = root.getDouble("angle");
+            mUseAngle = root.getBoolean("useAngle");
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    protected GPSPoint  mLocation;
+    protected double    mRadius;
+    protected double    mField;
+    protected double    mAngle;
+    protected boolean   mUseAngle;
+    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);
+            js.put("useAngle", ""+mUseAngle);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return js;
+    }
+
+    public boolean isPlayerOn(Player p)
+    {
+        GPSPoint pp = p.getPosition();
+        if(pp==null || p.getSensorsManager().getPosition()==null) return false;
+
+        return mLocation.getDistanceWith(pp)<=mRadius+p.getSensorsManager().getPosition().getAccuracy();
+    }
+
+    public boolean isOnDirection(Player p)
+    {
+        boolean ret;
+        //si utilisation de angle et field
+        if(mUseAngle)
+        {
+            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);
+            }
+        }
+        else
+        //si par rapport au coordonnées gps
+        {
+            float pangle=p.getOrientation()%360;
+            while (pangle < 0) pangle += 360;
+            float dir = (float)p.getPosition().getAngleWith(mLocation);
+            double min = dir - mField / 2;
+            double max = dir + mField / 2;
+            if (min < 0) {
+                min += 360;
+                ret = pangle >= min || pangle <= max;
+            } else if (max >= 360) {
+                max -= 360;
+                ret = pangle <= max || (pangle >= min);
+            } else {
+                ret = (pangle <= max && pangle >= min);
+            }
+        }
+        return ret;
+    }
+
+    public boolean isUseAngle()
+    {
+        return mUseAngle;
+    }
+
+    public double getDistance(Player p)
+    {
+        return mLocation.getDistanceWith(p.getPosition());
+    }
+
+}

+ 148 - 0
app/src/main/java/app/mar/utils/game/Player.java

@@ -0,0 +1,148 @@
+package app.mar.utils.game;
+
+import android.app.Activity;
+
+import java.io.Serializable;
+
+import app.mar.activities.ARActivity;
+import app.mar.utils.SensorsManager;
+import app.mar.utils.geometry.GPSPoint;
+
+/**
+ * Created by ptitcois on 20/08/16.
+ */
+public class Player implements Serializable{
+    protected transient SensorsManager mSensors;
+
+    private static final int STATE_SEARCHING=0;
+    private static final int STATE_FOUND=1;
+    private int mState=STATE_FOUND;
+    private GPSPoint mFreezedPosition;
+
+    public Player(Activity sm)
+    {
+        mSensors=new SensorsManager(sm);
+    }
+
+    public void setSensorsManager(SensorsManager sm)
+    {
+        mSensors=sm;
+    }
+
+    public SensorsManager getSensorsManager()
+    {
+        return mSensors;
+    }
+
+    public void onPause()
+    {
+        mSensors.onPause();
+    }
+
+    public boolean hasMagneticField() {
+        return mSensors.hasMagneticField();
+    }
+
+    public boolean hasAcceleromter() {
+        return mSensors.hasAcceleromter();
+    }
+
+    public void onResume(ARActivity act)
+    {
+        mSensors.onResume();
+    }
+
+
+    public float getOrientation()
+    {
+        System.out.println("Orientation = "+mSensors.getOrientation()); return mSensors.getOrientation();
+    }
+
+    public float getOrientationX()
+    {
+
+        System.out.println("Orientation x = "+mSensors.getAzimuth());return mSensors.getAzimuth();
+    }
+
+    public float getOrientationY()
+    {
+
+        System.out.println("Orientation y = "+mSensors.getAngleY()); return mSensors.getAngleY();
+    }
+
+
+    public float getOrientationZ()
+    {
+        return mSensors.getAngleZ();
+    }
+
+    public double getAngleWith(Place p)
+    {
+        double d=getPosition().getAngleWith(p.getLocation());;
+        while(d<0) d+=360;
+        return d;
+    }
+
+    public double getXWith(Place p)
+    {
+        return getPosition().getDistanceXWith(p.getLocation());
+    }
+
+    public double getYWith(Place p)
+    {
+        return getPosition().getDistanceYWith(p.getLocation());
+    }
+
+    public double getRelXWith(Place p)
+    {
+        return getPosition().getRelXWith(p.getLocation());
+    }
+
+    public double getRelYWith(Place p)
+    {
+        return getPosition().getRelYWith(p.getLocation());
+    }
+
+    public void newSensorManager(Activity act)
+    {
+        mSensors = new SensorsManager(act);
+    }
+
+    public void nextStub()
+    {
+        mSensors.nextStub();
+    }
+
+    public GPSPoint getPosition()
+    {
+        return mSensors.getPosition();
+        /*
+        if(mState==STATE_SEARCHING || mSensors.useGpsStub())
+            return mSensors.getPosition();
+        else
+            return mFreezedPosition;
+            */
+    }
+
+    public void freezePosition()
+    {
+        return ;
+        /*mState=STATE_FOUND;
+        mFreezedPosition=mSensors.getPosition().copy();*/
+    }
+
+    public void freezePosition(GPSPoint target)
+    {
+        return ;
+        /*if( mState==STATE_SEARCHING
+                || (mSensors.getPosition()!=null &&  mFreezedPosition!=null &&
+                mSensors.getPosition().getDistanceWith(target) < mFreezedPosition.getDistanceWith(target)))
+            mFreezedPosition=mSensors.getPosition().copy();
+        mState=STATE_FOUND;*/
+    }
+
+    public void releasePosition()
+    {
+        mState=STATE_SEARCHING;
+    }
+}

+ 188 - 0
app/src/main/java/app/mar/utils/game/Resource.java

@@ -0,0 +1,188 @@
+package app.mar.utils.game;
+
+import android.app.Activity;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import app.mar.utils.files.JSONAssetsManager;
+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 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 boolean isLoaded = false;
+    protected int   mStage;
+    protected String mType = ""; //3D Image Audio Video
+    protected String mTitle= ""; //3D Image Audio Video
+
+    protected void loadResourceData(Activity act)
+    {
+        System.out.println("Loading ressource res/raw/"+mName+"_res");
+        JSONObject obj = JSONAssetsManager.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);
+
+
+            mType = obj.getString("type");
+            mTitle = obj.getString("title");
+            mStage = obj.getInt("stage");
+            isLoaded=true;
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Number3d getPosition() {
+        return mPosition;
+    }
+
+    public Number3d getRotation() {
+        return mRotation;
+    }
+
+    public Number3d getScale() {
+        return mScale;
+    }
+
+    public Resource(String name, Activity act)
+    {
+        mName=name;
+        loadResourceData(act);
+    }
+
+    public Resource(JSONObject obj, Activity act)
+    {
+        try {
+            mName=obj.getString("name");
+            loadResourceData(act);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getType() {
+        return mType;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+
+    public String getComment() {
+        return mComment;
+    }
+
+    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)
+        {
+            Log.e("resource.get3Model", "app.brest.testmin3d:raw/"+mName+"_obj");
+            IParser myParser = Parser.createParser(context, Parser.Type.OBJ,  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;
+            Log.e("____","Rotation ("+m3DModel.rotation().x+","+m3DModel.rotation().y+","+m3DModel.rotation().z+")");
+            m3DModel.rotation().x+=45;
+            //m3DModel.rotation().y+=-180;
+
+            //m3DModel.rotation().y=90;
+            m3DModel.scale().x = mScale.x; m3DModel.scale().y = mScale.y; m3DModel.scale().z = mScale.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;
+    }
+
+    public String toString()
+    {
+        return mTitle;
+    }
+
+    public boolean isAudio()
+    {
+        return mType.toLowerCase().compareTo("audio")==0;
+    }
+
+    public boolean isVideo()
+    {
+        return mType.toLowerCase().compareTo("video")==0;
+    }
+    public boolean is3D()
+    {
+        return mType.toLowerCase().compareTo("3d")==0;
+    }
+
+    public boolean isImage()
+    {
+        return mType.toLowerCase().compareTo("image")==0;
+    }
+
+}

+ 82 - 0
app/src/main/java/app/mar/utils/game/Stage.java

@@ -0,0 +1,82 @@
+package app.mar.utils.game;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+/**
+ * Created by ptitcois on 27/08/16.
+ */
+public class Stage implements Serializable {
+    public Stage()
+    {
+
+    }
+
+    public Stage(JSONObject obj)
+    {
+        try {
+            System.out.println("Obj: "+obj.toString());
+            mStage=obj.getInt("stage");
+            mResourceName=obj.getString("resource");
+            mNResourceStep=obj.getInt("n_resource_step");
+            mIsTransition=obj.getBoolean("transition");
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public  String toString()
+    {
+        String s = "";
+        s+="Stage="+mStage+"\n";
+        s+="Resource="+mResourceName+"\n";
+        s+="Step="+mNResourceStep+"\n";
+        s+="Transition="+mIsTransition+"\n";
+        return s;
+    }
+
+    protected int mStage;
+    protected Resource mResource;
+    protected String mResourceName;
+    protected int mNResourceStep;
+    protected boolean mIsTransition=true;
+
+    public int getStage() {
+        return mStage;
+    }
+
+    public void setStage(int mStage) {
+        this.mStage = mStage;
+    }
+
+    public Resource getResource() {
+        return mResource;
+    }
+
+    public void setResource(Resource mResource) {
+        this.mResource = mResource;
+    }
+
+    public int getNResourceStep()
+    {
+        return mNResourceStep;
+    }
+
+    public String getResourceName() {
+        return mResourceName;
+    }
+
+    public void setResourceName(String mResourceName) {
+        this.mResourceName = mResourceName;
+    }
+
+    public boolean isTransition() {
+        return mIsTransition;
+    }
+
+    public void setIsTransition(boolean mIsTransition) {
+        this.mIsTransition = mIsTransition;
+    }
+}

+ 134 - 0
app/src/main/java/app/mar/utils/geometry/GPSPoint.java

@@ -0,0 +1,134 @@
+package app.mar.utils.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Created by ptitcois on 16/08/16.
+ */
+public class GPSPoint extends app.mar.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 GPSPoint copy()
+    {
+        return new GPSPoint(mX, mY, mAccuracy);
+    }
+
+    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 getRelXWith(GPSPoint _b)
+    {
+        return _b.mX-mX;
+    }
+
+
+    public double getDistanceYWith(GPSPoint _b)
+    {
+        Point b = new GPSPoint(_b);
+        b.mX=mX;
+
+        return getDistanceWith(b);
+    }
+
+
+
+
+    public double getRelYWith(GPSPoint _b)
+    {
+
+        return _b.mY-mY;
+    }
+
+    public double getAngleWith(GPSPoint p)
+    {
+        double dx = getRelXWith(p);
+        double dy = getRelYWith(p);
+        if(dy>0) return -Math.atan(dx/dy)*180/Math.PI;
+        else return 180-Math.atan(dx/dy)*180/Math.PI;
+    }
+
+    public String toString()
+    {
+        return "("+mX+", "+mY+" : "+mAccuracy+")";
+
+    }
+    /*
+            Opposé
+        _________________
+         \               |
+           \             |
+             \           |
+               \         |  Adjacent
+                 \       |
+                   \     |
+                     \ /\|
+                       \ |
+                         \Alpha=Atan(oppose/adjacent)=atan(x/y)
+
+
+
+
+     */
+}

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

@@ -0,0 +1,98 @@
+package app.mar.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);
+    }
+
+}

+ 118 - 0
app/src/main/java/app/mar/utils/geometry/Shape.java

@@ -0,0 +1,118 @@
+package app.mar.utils.geometry;
+/**
+ * Created by François Gautrais on 16/08/16.
+ * Support also GPS Shape (By using GPSPoint)
+ */
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+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);
+	}
+}

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

@@ -0,0 +1,684 @@
+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()");
+
+		Log.i("-----", "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
+		//
+	}
+}

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

@@ -0,0 +1,218 @@
+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;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * 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, View.OnTouchListener
+{
+	public Scene scene;
+	public Renderer mRender;
+	protected GLSurfaceView _glSurfaceView;
+	
+	protected Handler _initSceneHander;
+	protected Handler _updateSceneHander;
+	
+    private boolean _renderContinuously;
+    protected float mPreviousX;
+	protected float mPreviousY;
+
+	final Runnable _initSceneRunnable = new Runnable() 
+	{
+        public void run() {
+            onInitScene();
+        }
+    };
+    
+	final Runnable _updateSceneRunnable = new Runnable() 
+    {
+        public void run() {
+            onUpdateScene();
+        }
+    };
+    
+
+	public 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);
+		_glSurfaceView.setOnTouchListener(this);
+
+		//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;
+    }
+
+	@Override
+	public boolean onTouch(View view, MotionEvent motionEvent) {
+		return true;
+	}
+
+	public void setOnToucheListener(View.OnTouchListener d)
+	{
+		_glSurfaceView.setOnTouchListener(d);
+	}
+}

+ 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;
+	}
+}

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

@@ -0,0 +1,429 @@
+package min3d.parser;
+
+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 app.mar.utils.files.FileManager;
+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.Context;
+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;
+	protected InputStream mStream=null;
+	protected String 	  mPath;
+	protected Context mContext=null;
+
+	public AParser(Context a)
+	{
+		mContext=a;
+		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(Context a, Resources resources, String resourceID, Boolean generateMipMap)
+	{
+		this(a);
+		mContext=a;
+		this.resources = resources;
+		this.resourceID = resourceID;
+		if (resourceID.indexOf(":") > -1)
+			this.packageID = resourceID.split(":")[0];
+		this.generateMipMap = generateMipMap;
+	}
+
+	public AParser(Context a, String path, Boolean generateMipMap)
+	{
+		this(a);
+		mContext=a;
+		mPath=path;
+		try {
+			mStream= FileManager.openFile(a,path);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		int d = mPath.lastIndexOf('/');
+		if(d>=0) packageID=mPath.substring(0,d);
+		else packageID="";
+		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
+		 * @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
+		 */
+		public void addBitmapAsset(BitmapAsset ba) {
+			BitmapAsset existingBA = getBitmapAssetByResourceID(ba.resourceID);
+
+			if(existingBA == null)
+			{
+				Bitmap b=null;
+				if(mStream==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);
+
+					b = Utils.makeBitmapFromResourceId(bmResourceID);
+				}else
+				{
+					try {
+						b=FileManager.openImage(mContext, ba.resourceID);
+					} catch (Exception e) {
+						e.printStackTrace();
+					}
+				}
+				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
+}

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

@@ -0,0 +1,252 @@
+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.Context;
+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(Context c, Resources resources, String resourceID, boolean generateMipMap) {
+		super(c, resources, resourceID, generateMipMap);
+	}
+
+
+	public MD2Parser(Context c, String is, boolean generateMipMap) {
+		super(c, is, 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 = null;
+		if(mStream==null)
+		{
+			fileIn =resources.openRawResource(resources.getIdentifier(
+					resourceID, null, null));
+		}else fileIn = mStream;
+		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);
+		}
+	}
+}

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

@@ -0,0 +1,241 @@
+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.Context;
+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(Context c, Resources resources, String resourceID, boolean generateMipMap) {
+		super(c, resources, resourceID, generateMipMap);
+	}
+
+
+	public Max3DSParser(Context c, String is, boolean generateMipMap) {
+		super(c,is, generateMipMap);
+	}
+
+	@Override
+	public void parse() {
+		InputStream fileIn = null;
+		if(mStream==null)
+		{
+			fileIn =resources.openRawResource(resources.getIdentifier(
+					resourceID, null, null));
+		}else fileIn = mStream;
+		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;
+	}
+}

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

@@ -0,0 +1,286 @@
+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.StringTokenizer;
+
+import app.mar.utils.files.FileManager;
+import min3d.Min3d;
+import min3d.Shared;
+import min3d.core.Object3dContainer;
+import min3d.vos.Color4;
+import min3d.vos.Number3d;
+import min3d.vos.Uv;
+
+import android.content.Context;
+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
+ * 
+ */
+
+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(Context a, Resources resources, String resourceID, boolean generateMipMap) {
+		super(a, resources, resourceID, generateMipMap);
+		System.out.println("Parser:"+resourceID);
+		System.err.println("Parser:"+resourceID);
+	}
+
+	public ObjParser(Context a, String is, boolean generateMipMap) {
+		super(a, is, generateMipMap);
+		System.out.println("Parser:"+resourceID);
+		System.err.println("Parser:"+resourceID);
+	}
+
+	@Override
+	public void parse() {
+		long startTime = Calendar.getInstance().getTimeInMillis();
+
+		InputStream fileIn = null;
+		if(mStream==null)
+		{
+			fileIn =resources.openRawResource(resources.getIdentifier(
+					resourceID, null, null));
+		}else fileIn = mStream;
+		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) {
+		InputStream fileIn=null;
+		StringBuffer libIDSbuf = new StringBuffer(libID);
+		int dotIndex = libIDSbuf.lastIndexOf(".");
+		if (dotIndex > -1)
+			libIDSbuf = libIDSbuf.replace(dotIndex, dotIndex + 1, "_");
+		if(mStream==null) {
+			StringBuffer resourceID = new StringBuffer(packageID);
+
+
+			resourceID.append(":raw/");
+			resourceID.append(libIDSbuf.toString());
+			Log.e("OBJParser", "Load MTL '" + resourceID.toString() + "'");
+			fileIn = resources.openRawResource(resources.getIdentifier(
+					resourceID.toString(), null, null));
+		}else {
+			try {
+				fileIn = FileManager.openFile(mContext, packageID+libID);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+
+
+		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);
+						if(mStream==null) 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);
+						System.out.println("Drawable = "+texture.toString());
+						/*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;
+
+}

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

@@ -0,0 +1,58 @@
+package min3d.parser;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import java.io.InputStream;
+
+/**
+ * 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(Context c, Type type, Resources resources, String resourceID, boolean generateMipMap)
+	{
+		switch(type)
+		{
+			case OBJ:
+				return new ObjParser(c,resources, resourceID, generateMipMap);
+			case MAX_3DS:
+				return new Max3DSParser(c,resources, resourceID, generateMipMap);
+			case MD2:
+				return new MD2Parser(c,resources, resourceID, generateMipMap);
+		}
+		
+		return null;
+	}
+
+	public static IParser createParser(Context c, Type type, String is, boolean generateMipMap)
+	{
+		switch(type)
+		{
+			case OBJ:
+				return new ObjParser(c,is, generateMipMap);
+			case MAX_3DS:
+				return new Max3DSParser(c,is, generateMipMap);
+			case MD2:
+				return new MD2Parser(c,is, 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, 1); // ... 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;
+	}
+}

Some files were not shown because too many files changed in this diff