diff --git a/README.md b/README.md index 55df3117..e5989b8c 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,12 @@ The application does not use any third party library. * Collada format (DAE): https://en.wikipedia.org/wiki/COLLADA -News (02/02/2022) +News (25/07/2022) ================= -* /¡\ repo moved to the3deers organization + MIT License 2020 -* New version released 3.2.0 -* Repository Explorer improved -* Smoothing implementation fixed -* Fixed memory leak - - +* New version released 3.3.0 +* New orthographic, isometric and free camera views +* Interactive object orientation Demo ==== @@ -75,7 +71,7 @@ Features - OpenGL ES 2.0 API - Formats: OBJ (wavefront), STL (STereoLithography) & DAE (Collada-BETA) - calculation of normals - - transformations: scaling, rotation, translation + - transformations: scaling, rotation, translation, orientation - colors - textures - lighting @@ -86,6 +82,7 @@ Features - skybox - object pick - camera support + - perspective, orthographic and isometric views - tap to select object - drag to move camera - rotate with 2 fingers to rotate camera @@ -196,6 +193,13 @@ ChangeLog (f) fixed, (i) improved, (n) new feature +- 3.3.0 (23/06/2022) + - (n) interactive object orientation + - (n) isometric, orthographic and free camera view + - (n) New gui axis + gui info + - (f) fixed FPS counter + - (i) some user options are being saved (camera settings) + - 3.2.0 (02/02/2022) - (i) repository explorer improved - multiple index files - (f) smoothing fixed diff --git a/app/build/outputs/apk/release/app-release.apk b/app/build/outputs/apk/release/app-release.apk index 53c49a82..b20ce1d8 100644 Binary files a/app/build/outputs/apk/release/app-release.apk and b/app/build/outputs/apk/release/app-release.apk differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e0ebbfdf..be2ac421 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="32" + android:versionName="3.3.0"> @@ -49,6 +49,11 @@ + + diff --git a/app/src/main/java/org/andresoviedo/app/model3D/demo/DemoLoaderTask.java b/app/src/main/java/org/andresoviedo/app/model3D/demo/DemoLoaderTask.java index 0e43e8b4..5056aa86 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/demo/DemoLoaderTask.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/demo/DemoLoaderTask.java @@ -153,9 +153,9 @@ public void onLoad(Object3DData obj53) { Object3DData obj53 = new WavefrontLoader(GLES20.GL_TRIANGLE_FAN, new LoadListenerAdapter(){ @Override public void onLoad(Object3DData obj53) { - obj53.setLocation(new float[] { 2f, 0f, 0f }); obj53.setColor(new float[] { 1.0f, 1.0f, 1f, 1.0f }); Rescaler.rescale(obj53, 2f); + obj53.setLocation(new float[] { 2f, 0f, 0f }); DemoLoaderTask.this.onLoad(obj53); } }).load(new URI("android://org.andresoviedo.dddmodel2/assets/models/ToyPlane.obj")).get(0); @@ -171,10 +171,10 @@ public void onLoad(Object3DData obj53) { Object3DData obj53 = new ColladaLoader().load(new URI("android://org.andresoviedo.dddmodel2/assets/models/cowboy.dae"), new LoadListenerAdapter(){ @Override public void onLoad(Object3DData obj53) { - obj53.setLocation(new float[] { 0f, -1f, 1f}); obj53.setColor(new float[] { 1.0f, 1.0f, 1f, 1.0f }); - obj53.setRotation(new float[]{-90,0,0}); Rescaler.rescale(obj53, 2f); + obj53.setLocation(new float[] { 0f, 0f, 2f}); + obj53.setCentered(true); DemoLoaderTask.this.onLoad(obj53); } }).get(0); diff --git a/app/src/main/java/org/andresoviedo/app/model3D/demo/GlyphsDemoActivity.java b/app/src/main/java/org/andresoviedo/app/model3D/demo/GlyphsDemoActivity.java new file mode 100644 index 00000000..bd2e0238 --- /dev/null +++ b/app/src/main/java/org/andresoviedo/app/model3D/demo/GlyphsDemoActivity.java @@ -0,0 +1,91 @@ +package org.andresoviedo.app.model3D.demo; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import org.andresoviedo.android_3d_model_engine.gui.Text; +import org.andresoviedo.android_3d_model_engine.gui.Widget; +import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.android_3d_model_engine.model.Constants; +import org.andresoviedo.android_3d_model_engine.model.Projection; +import org.andresoviedo.android_3d_model_engine.services.SceneLoader; +import org.andresoviedo.android_3d_model_engine.view.ModelSurfaceView; +import org.andresoviedo.android_3d_model_engine.view.ViewEvent; +import org.andresoviedo.util.event.EventListener; + +import java.util.EventObject; + +/** + * This activity represents the container for our 3D viewer. + * + * @author andresoviedo + */ +public class GlyphsDemoActivity extends Activity implements EventListener { + + private ModelSurfaceView glView; + private SceneLoader scene; + private Camera camera; + + private Text abcd; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.i("GlyphsDemoActivity", "onCreate: Loading activity... "+savedInstanceState); + super.onCreate(savedInstanceState); + + try { + // Create our 3D scenario + Log.i("GlyphsDemoActivity", "Creating Scene..."); + scene = new SceneLoader(this); + scene.addListener(this); + + // Camera setup + final Camera camera = new Camera(Constants.UNIT); + camera.setProjection(Projection.PERSPECTIVE); + camera.setChanged(true); + scene.setCamera(camera); + + + Log.i("GlyphsDemoActivity", "Loading GLSurfaceView..."); + glView = new ModelSurfaceView(this, Constants.COLOR_GRAY, this.scene); + glView.addListener(this); + glView.setProjection(Projection.PERSPECTIVE); + setContentView(glView); + + + abcd = Text.allocate(10, 6); + abcd.setPadding(Widget.PADDING_01); + abcd.update("abcdefghij\n" + + "klmnopqrst\n" + + "uvwxyz\n" + + "ABCDEFGHIJ\n" + + "KLMNOPQRST\n" + + "UVWXYZ"); + abcd.setVisible(true); + scene.addObject(abcd); + + + } catch (Exception e) { + Log.e("GlyphsDemoActivity", e.getMessage(), e); + Toast.makeText(this, "Error loading OpenGL view:\n" + e.getMessage(), Toast.LENGTH_LONG).show(); + } + + } + + @Override + public boolean onEvent(EventObject event) { + if (event instanceof ViewEvent) { + ViewEvent viewEvent = (ViewEvent) event; + if (viewEvent.getCode() == ViewEvent.Code.SURFACE_CHANGED) { + abcd.setScale(Constants.UNIT/5,Constants.UNIT/5,Constants.UNIT/5); + float ratio = (float) viewEvent.getWidth() / viewEvent.getHeight(); + float x = -ratio * Constants.UNIT + abcd.getCurrentDimensions().getWidth() / 2 - abcd.getCurrentDimensions().getCenter()[0] + Constants.UNIT * 0.05f; + float y = 1 * Constants.UNIT - abcd.getCurrentDimensions().getHeight() / 2 - abcd.getCurrentDimensions().getCenter()[1] - Constants.UNIT * 0.05f; + abcd.setLocation( new float[]{x,y,0}); + } + } + return false; + } +} diff --git a/app/src/main/java/org/andresoviedo/app/model3D/view/MenuActivity.java b/app/src/main/java/org/andresoviedo/app/model3D/view/MenuActivity.java index 8c98115e..0ce305d9 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/view/MenuActivity.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/view/MenuActivity.java @@ -18,6 +18,7 @@ import org.andresoviedo.android_3d_model_engine.services.collada.ColladaLoader; import org.andresoviedo.android_3d_model_engine.services.wavefront.WavefrontLoader; +import org.andresoviedo.app.model3D.demo.GlyphsDemoActivity; import org.andresoviedo.dddmodel2.R; import org.andresoviedo.util.android.AndroidUtils; import org.andresoviedo.util.android.AssetUtils; @@ -51,7 +52,7 @@ public class MenuActivity extends ListActivity { private enum Action { - LOAD_MODEL, CARGAR_MODELO, GITHUB, SETTINGS, HELP, AYUDA, ABOUT, ACERCA, EXIT, SALIR, UNKNOWN, DEMO + LOAD_MODEL, CARGAR_MODELO, GITHUB, SETTINGS, HELP, AYUDA, ABOUT, ACERCA, EXIT, SALIR, UNKNOWN, DEMO, DEMOS } /** @@ -78,7 +79,9 @@ protected void onCreate(Bundle savedInstanceState) { public void onListItemClick(ListView l, View v, int position, long id) { String selectedItem = (String) getListView().getItemAtPosition(position); // Toast.makeText(getApplicationContext(), "Click ListItem '" + selectedItem + "'", Toast.LENGTH_LONG).show(); - String selectedAction = selectedItem.replace(' ', '_').toUpperCase(Locale.getDefault()); + String selectedAction = selectedItem.replace(' ', '_') + .replaceAll("\\.", "") + .toUpperCase(Locale.getDefault()); Action action = Action.UNKNOWN; try { action = Action.valueOf(selectedAction); @@ -87,6 +90,21 @@ public void onListItemClick(ListView l, View v, int position, long id) { } try { switch (action) { + case DEMOS: + ContentUtils.showListDialog(this, "Demos List", new String[]{"Random Objects", "GUI"}, (DialogInterface dialog, int which) -> { + if (which == 0) { + Intent demoIntent = new Intent(MenuActivity.this.getApplicationContext(), ModelActivity.class); + demoIntent.putExtra("immersiveMode", "false"); + demoIntent.putExtra("backgroundColor", "0 0 0 1"); + MenuActivity.this.startActivity(demoIntent); + } else if (which == 1) { + Intent demoIntent = new Intent(MenuActivity.this.getApplicationContext(), GlyphsDemoActivity.class); + MenuActivity.this.startActivity(demoIntent); + } else { + // TODO: + } + }); + break; case DEMO: Intent demoIntent = new Intent(MenuActivity.this.getApplicationContext(), ModelActivity.class); demoIntent.putExtra("immersiveMode", "false"); diff --git a/app/src/main/java/org/andresoviedo/app/model3D/view/ModelActivity.java b/app/src/main/java/org/andresoviedo/app/model3D/view/ModelActivity.java index 2d513982..c01cb139 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/view/ModelActivity.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/view/ModelActivity.java @@ -3,7 +3,12 @@ import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.Context; import android.content.Intent; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -11,16 +16,25 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.View; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import org.andresoviedo.android_3d_model_engine.camera.CameraController; import org.andresoviedo.android_3d_model_engine.collision.CollisionController; +import org.andresoviedo.android_3d_model_engine.collision.CollisionEvent; import org.andresoviedo.android_3d_model_engine.controller.TouchController; +import org.andresoviedo.android_3d_model_engine.controller.TouchEvent; +import org.andresoviedo.android_3d_model_engine.event.SelectedObjectEvent; +import org.andresoviedo.android_3d_model_engine.model.Projection; import org.andresoviedo.android_3d_model_engine.services.LoaderTask; import org.andresoviedo.android_3d_model_engine.services.SceneLoader; -import org.andresoviedo.android_3d_model_engine.view.ModelRenderer; +import org.andresoviedo.android_3d_model_engine.view.FPSEvent; import org.andresoviedo.android_3d_model_engine.view.ModelSurfaceView; +import org.andresoviedo.android_3d_model_engine.view.ViewEvent; import org.andresoviedo.app.model3D.demo.DemoLoaderTask; import org.andresoviedo.dddmodel2.R; import org.andresoviedo.util.android.ContentUtils; @@ -57,7 +71,7 @@ public class ModelActivity extends Activity implements EventListener { */ private float[] backgroundColor = new float[]{0.0f, 0.0f, 0.0f, 1.0f}; - private ModelSurfaceView gLView; + private ModelSurfaceView glView; private TouchController touchController; private SceneLoader scene; private ModelViewerGUI gui; @@ -67,9 +81,12 @@ public class ModelActivity extends Activity implements EventListener { private Handler handler; private CameraController cameraController; + private SensorManager sensorManager; + private Sensor sensor; + @Override protected void onCreate(Bundle savedInstanceState) { - Log.i("ModelActivity", "Loading activity..."); + Log.i("ModelActivity", "onCreate: Loading activity... "+savedInstanceState); super.onCreate(savedInstanceState); // Try to get input parameters @@ -100,7 +117,8 @@ protected void onCreate(Bundle savedInstanceState) { // Create our 3D scenario Log.i("ModelActivity", "Loading Scene..."); - scene = new SceneLoader(this, paramUri, paramType, gLView); + scene = new SceneLoader(this, paramUri, paramType); + scene.addListener(this); if (paramUri == null) { final LoaderTask task = new DemoLoaderTask(this, null, scene); task.execute(); @@ -115,10 +133,10 @@ protected void onCreate(Bundle savedInstanceState) { try { Log.i("ModelActivity", "Loading GLSurfaceView..."); - gLView = new ModelSurfaceView(this, backgroundColor, this.scene); - gLView.addListener(this); - setContentView(gLView); - scene.setView(gLView); + glView = new ModelSurfaceView(this, backgroundColor, this.scene); + glView.addListener(this); + setContentView(glView); +// scene.setView(glView); } catch (Exception e) { Log.e("ModelActivity", e.getMessage(), e); Toast.makeText(this, "Error loading OpenGL view:\n" + e.getMessage(), Toast.LENGTH_LONG).show(); @@ -128,6 +146,7 @@ protected void onCreate(Bundle savedInstanceState) { Log.i("ModelActivity", "Loading TouchController..."); touchController = new TouchController(this); touchController.addListener(this); + //touchController.addListener(glView); } catch (Exception e) { Log.e("ModelActivity", e.getMessage(), e); Toast.makeText(this, "Error loading TouchController:\n" + e.getMessage(), Toast.LENGTH_LONG).show(); @@ -135,10 +154,10 @@ protected void onCreate(Bundle savedInstanceState) { try { Log.i("ModelActivity", "Loading CollisionController..."); - collisionController = new CollisionController(gLView, scene); - collisionController.addListener(scene); - touchController.addListener(collisionController); - touchController.addListener(scene); + collisionController = new CollisionController(glView, scene); + collisionController.addListener(this); + //touchController.addListener(collisionController); + //touchController.addListener(scene); } catch (Exception e) { Log.e("ModelActivity", e.getMessage(), e); Toast.makeText(this, "Error loading CollisionController\n" + e.getMessage(), Toast.LENGTH_LONG).show(); @@ -147,8 +166,8 @@ protected void onCreate(Bundle savedInstanceState) { try { Log.i("ModelActivity", "Loading CameraController..."); cameraController = new CameraController(scene.getCamera()); - gLView.getModelRenderer().addListener(cameraController); - touchController.addListener(cameraController); + //glView.getModelRenderer().addListener(cameraController); + //touchController.addListener(cameraController); } catch (Exception e) { Log.e("ModelActivity", e.getMessage(), e); Toast.makeText(this, "Error loading CameraController" + e.getMessage(), Toast.LENGTH_LONG).show(); @@ -157,9 +176,9 @@ protected void onCreate(Bundle savedInstanceState) { try { // TODO: finish UI implementation Log.i("ModelActivity", "Loading GUI..."); - gui = new ModelViewerGUI(gLView, scene); + gui = new ModelViewerGUI(glView, scene); touchController.addListener(gui); - gLView.addListener(gui); + glView.addListener(gui); scene.addGUIObject(gui); } catch (Exception e) { Log.e("ModelActivity", e.getMessage(), e); @@ -171,12 +190,84 @@ protected void onCreate(Bundle savedInstanceState) { setupOnSystemVisibilityChangeListener(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + setupOrientationListener(); + } + // load model scene.init(); Log.i("ModelActivity", "Finished loading"); } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + private void setupOrientationListener() { + try { + //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); + //sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); + sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR); + if (sensor != null) { + sensorManager.registerListener(new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + /*Log.v("ModelActivity","sensor: "+ Arrays.toString(event.values)); + Quaternion orientation = new Quaternion(event.values); + orientation.normalize(); + //scene.getSelectedObject().setOrientation(orientation); + glView.setOrientation(orientation);*/ + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + }, sensor, + SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI); + } + OrientationEventListener mOrientationListener = new OrientationEventListener( + getApplicationContext()) { + @Override + public void onOrientationChanged(int orientation) { + //scene.onOrientationChanged(orientation); + } + }; + + if (mOrientationListener.canDetectOrientation()) { + mOrientationListener.enable(); + } + } catch (Exception e) { + Log.e("ModelActivity","There is an issue setting up sensors",e); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putFloatArray("camera.pos",scene.getCamera().getPos()); + outState.putFloatArray("camera.view",scene.getCamera().getView()); + outState.putFloatArray("camera.up",scene.getCamera().getUp()); + outState.putString("renderer.projection",glView.getProjection().name()); + outState.putInt("renderer.skybox",glView.getSkyBoxId()); + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + if(state.containsKey("renderer.projection")) { + glView.setProjection(Projection.valueOf(state.getString("renderer.projection"))); + } + if(state.containsKey("camera.pos") && state.containsKey("camera.view") && state.containsKey("camera.up")){ + Log.d("ModelActivity","onRestoreInstanceState: Restoring camera settings..."); + scene.getCamera().set( + state.getFloatArray("camera.pos"), + state.getFloatArray("camera.view"), + state.getFloatArray("camera.up")); + } + if(state.containsKey("renderer.skybox")){ + glView.setSkyBox(state.getInt("renderer.skybox")); + } + } + + /** * Set up the {@link android.app.ActionBar}, if the API is available. */ @@ -220,6 +311,9 @@ public void onWindowFocusChanged(boolean hasFocus) { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.model_toggle_projection: + glView.toggleProjection(); + break; case R.id.model_toggle_wireframe: scene.toggleWireframe(); break; @@ -227,7 +321,7 @@ public boolean onOptionsItemSelected(MenuItem item) { scene.toggleBoundingBox(); break; case R.id.model_toggle_skybox: - gLView.toggleSkyBox(); + glView.toggleSkyBox(); break; case R.id.model_toggle_textures: scene.toggleTextures(); @@ -358,17 +452,50 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override public boolean onEvent(EventObject event) { - if (event instanceof ModelRenderer.ViewEvent) { - ModelRenderer.ViewEvent viewEvent = (ModelRenderer.ViewEvent) event; - if (viewEvent.getCode() == ModelRenderer.ViewEvent.Code.SURFACE_CHANGED) { - touchController.setSize(viewEvent.getWidth(), viewEvent.getHeight()); - gLView.setTouchController(touchController); + if (event instanceof FPSEvent){ + gui.onEvent(event); + } + else if (event instanceof SelectedObjectEvent){ + gui.onEvent(event); + } + else if (event.getSource() instanceof MotionEvent){ + // event coming from glview + touchController.onMotionEvent((MotionEvent) event.getSource()); + } + else if (event instanceof CollisionEvent){ + scene.onEvent(event); + } + else if (event instanceof TouchEvent){ + TouchEvent touchEvent = (TouchEvent) event; + if (touchEvent.getAction() == TouchEvent.Action.CLICK){ + if (!collisionController.onEvent(event)){ + scene.onEvent(event); + } + } else { + if (scene.getSelectedObject() != null) { + scene.onEvent(event); + } else { + cameraController.onEvent(event); + scene.onEvent(event); + if (((TouchEvent) event).getAction() == TouchEvent.Action.PINCH) { + glView.onEvent(event); + } + } + } + } + else if (event instanceof ViewEvent) { + ViewEvent viewEvent = (ViewEvent) event; + if (viewEvent.getCode() == ViewEvent.Code.SURFACE_CHANGED) { + cameraController.onEvent(viewEvent); + touchController.onEvent(viewEvent); // process event in GUI if (gui != null) { gui.setSize(viewEvent.getWidth(), viewEvent.getHeight()); gui.setVisible(true); } + } else if (viewEvent.getCode() == ViewEvent.Code.PROJECTION_CHANGED){ + cameraController.onEvent(event); } } return true; diff --git a/app/src/main/java/org/andresoviedo/app/model3D/view/ModelViewerGUI.java b/app/src/main/java/org/andresoviedo/app/model3D/view/ModelViewerGUI.java index e0560fe6..9eef7935 100644 --- a/app/src/main/java/org/andresoviedo/app/model3D/view/ModelViewerGUI.java +++ b/app/src/main/java/org/andresoviedo/app/model3D/view/ModelViewerGUI.java @@ -1,7 +1,10 @@ package org.andresoviedo.app.model3D.view; +import android.opengl.Matrix; import android.util.Log; +import org.andresoviedo.android_3d_model_engine.drawer.RendererFactory; +import org.andresoviedo.android_3d_model_engine.event.SelectedObjectEvent; import org.andresoviedo.android_3d_model_engine.gui.CheckList; import org.andresoviedo.android_3d_model_engine.gui.GUI; import org.andresoviedo.android_3d_model_engine.gui.Glyph; @@ -9,13 +12,18 @@ import org.andresoviedo.android_3d_model_engine.gui.Rotator; import org.andresoviedo.android_3d_model_engine.gui.Text; import org.andresoviedo.android_3d_model_engine.gui.Widget; +import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.android_3d_model_engine.model.Object3DData; +import org.andresoviedo.android_3d_model_engine.objects.Axis; import org.andresoviedo.android_3d_model_engine.services.SceneLoader; -import org.andresoviedo.android_3d_model_engine.view.ModelRenderer; +import org.andresoviedo.android_3d_model_engine.view.FPSEvent; import org.andresoviedo.android_3d_model_engine.view.ModelSurfaceView; +import org.andresoviedo.util.math.Quaternion; import java.util.ArrayList; import java.util.EventObject; import java.util.List; +import java.util.Locale; final class ModelViewerGUI extends GUI { @@ -23,6 +31,8 @@ final class ModelViewerGUI extends GUI { private final SceneLoader scene; private Text fps; + private Text info; + private Widget axis; private Widget icon; private Glyph icon2 = Glyph.build(Glyph.CHECKBOX_ON); private Menu3D menu; @@ -33,6 +43,7 @@ final class ModelViewerGUI extends GUI { this.glView = glView; this.scene = scene; setColor(new float[]{1, 1, 1, 0f}); + setPadding(Widget.PADDING_01); } /** @@ -47,6 +58,8 @@ public void setSize(int width, int height) { super.setSize(width, height); try { initFPS(); + initInfo(); + initAxis(); //initMenu(); //initMenu2(); }catch (Exception e){ @@ -57,6 +70,7 @@ public void setSize(int width, int height) { private void initFPS() { // frame-per-second + if (fps != null) return; fps = Text.allocate(7, 1); fps.setId("fps"); fps.setVisible(true); @@ -65,10 +79,51 @@ private void initFPS() { addWidget(fps); - fps.setPosition(GUI.POSITION_TOP_LEFT); + fps.setPosition(Widget.POSITION_TOP_LEFT); //addBackground(fps).setColor(new float[]{0.25f, 0.25f, 0.25f, 0.25f}); } + private void initInfo() { + // model info + if (info != null) return; + info = Text.allocate(15, 3, Text.PADDING_01); + info.setId("info"); + info.setVisible(true); + info.setParent(this); + //info.setRelativeScale(new float[]{0.85f,0.85f,0.85f}); + info.setRelativeScale(new float[]{0.25f,0.25f,0.25f}); + + addWidget(info); + + info.setPosition(Widget.POSITION_BOTTOM); + //addBackground(fps).setColor(new float[]{0.25f, 0.25f, 0.25f, 0.25f}); + } + + private void initAxis(){ + if (axis != null) return; + axis = new Widget(Axis.build()){ + final float[] matrix = new float[16]; + final Quaternion orientation = new Quaternion(matrix); + @Override + public void render(RendererFactory rendererFactory, Camera camera, float[] lightPosInWorldSpace, float[] colorMask) { + if (camera.hasChanged()){ + Matrix.setLookAtM(matrix,0,camera.getxPos(), camera.getyPos(), camera.getzPos(), + 0f,0f,0f, camera.getxUp(), camera.getyUp(), camera.getzUp()); + setOrientation(orientation); + } + super.render(rendererFactory, camera, lightPosInWorldSpace, colorMask); + } + }; + axis.setId("gui_axis"); + axis.setVisible(true); + axis.setParent(this); + axis.setRelativeScale(new float[]{0.1f,0.1f,0.1f}); + + addWidget(axis); + + axis.setPosition(Widget.POSITION_TOP_RIGHT); + } + private void initMenu2() { // checklist CheckList.Builder menuB = new CheckList.Builder(); @@ -97,7 +152,7 @@ private void initMenu() { icon.setParent(this); icon.setRelativeScale(new float[]{0.1f,0.1f,0.1f}); super.addWidget(icon); - icon.setPosition(GUI.POSITION_TOP_LEFT); + icon.setPosition(Widget.POSITION_TOP_LEFT); icon.setVisible(true); //super.addBackground(icon).setColor(new float[]{0.25f, 0.25f, 0.25f, 0.25f}); @@ -122,7 +177,7 @@ private void initMenu() { //icon.addListener(menu); super.addWidget(menu); - menu.setPosition(GUI.POSITION_MIDDLE); + menu.setPosition(Widget.POSITION_MIDDLE); super.addBackground(menu).setColor(new float[]{0.5f, 0f, 0f, 0.25f}); // menu rotator @@ -138,12 +193,36 @@ private void initMenu() { @Override public boolean onEvent(EventObject event) { super.onEvent(event); - if (event instanceof ModelRenderer.FPSEvent){ + if (event instanceof FPSEvent){ if (fps.isVisible()) { - ModelRenderer.FPSEvent fpsEvent = (ModelRenderer.FPSEvent) event; + FPSEvent fpsEvent = (FPSEvent) event; fps.update(fpsEvent.getFps() + " fps"); } } + else if (event instanceof SelectedObjectEvent){ + if (this.info.isVisible()){ + final Object3DData selected = ((SelectedObjectEvent) event).getSelected(); + final StringBuilder info = new StringBuilder(); + if (selected != null) { + if (selected.getId().indexOf('/') == -1){ + info.append(selected.getId()); + } else { + info.append(selected.getId().substring(selected.getId().lastIndexOf('/')+1)); + } + info.append('\n'); + info.append("size: "); + info.append(String.format(Locale.getDefault(), "%.2f",selected.getDimensions().getLargest())); + info.append('\n'); + info.append("scale: "); + info.append(String.format(Locale.getDefault(), "%.2f",selected.getScaleX())); + //final DecimalFormat df = new DecimalFormat("0.##"); + //info.append(df.format(selected.getScaleX())); + info.append("x"); + } + Log.v("ModelViewerGUI","Selected object info: "+info); + this.info.update(info.toString().toLowerCase()); + } + } else if (event instanceof Menu3D.ItemSelected) { switch (((Menu3D.ItemSelected) event).getSelected()) { case 0: diff --git a/app/src/main/res/menu/model.xml b/app/src/main/res/menu/model.xml index ba08e061..6f9bc179 100644 --- a/app/src/main/res/menu/model.xml +++ b/app/src/main/res/menu/model.xml @@ -1,78 +1,85 @@ + + - + + Math3DUtils.createRotationMatrixAroundVector(buffer, 0, dX, xArriba, yArriba, zArriba); + } else { + // in this case the user is drawing a vertical line: |^ v| + Math3DUtils.createRotationMatrixAroundVector(buffer, 0, dY, xRight, yRight, zRight); + } + + float[] newBuffer = new float[12]; + Math3DUtils.multiplyMMV(newBuffer, 0, buffer, 0, coordinates, 0); + + if (isOutOfBounds(newBuffer[0], newBuffer[1], newBuffer[2])) return; + + pos[0] = newBuffer[0]; + pos[1] = newBuffer[1]; + pos[2] = newBuffer[2]; + //view[0] = newBuffer[4]; + //view[1] = newBuffer[4 + 1]; + //view[2] = newBuffer[4 + 2]; + up[0] = newBuffer[8]; + up[1] = newBuffer[8 + 1]; + up[2] = newBuffer[8 + 2]; + Math3DUtils.normalize(up); + + delegate.setChanged(true); + save(); + } + + public synchronized void MoveCameraZ(float direction) { + //if (true) return; + // Moving the camera requires a little more then adding 1 to the z or + // subracting 1. + // First we need to get the direction at which we are looking. + float xLookDirection, yLookDirection, zLookDirection; + + // The look direction is the view minus the position (where we are). + xLookDirection = getxView() - pos[0]; + yLookDirection = getyView() - pos[1]; + zLookDirection = view[2] - pos[2]; + + // Normalize the direction. + float dp = Matrix.length(xLookDirection, yLookDirection, zLookDirection); + xLookDirection /= dp; + yLookDirection /= dp; + zLookDirection /= dp; + + float x = pos[0] + xLookDirection * direction; + float y = pos[1] + yLookDirection * direction; + float z = pos[2] + zLookDirection * direction; + + + if (isOutOfBounds(x, y, z)) return; + + pos[0] = x; + pos[1] = y; + pos[2] = z; + + save(); + + delegate.setChanged(true); + } + + @Override + public synchronized void Rotate(float angle) { + + if (angle == 0 || Float.isNaN(angle)) { + Log.w("DefaultCamera", "NaN"); + return; + } + float xLook = getxView() - pos[0]; + float yLook = getyView() - pos[1]; + float zLook = view[2] - pos[2]; + float vlen = Matrix.length(xLook, yLook, zLook); + xLook /= vlen; + yLook /= vlen; + zLook /= vlen; + + final float[] buffer = new float[16]; + Math3DUtils.createRotationMatrixAroundVector(buffer, 0, angle, xLook, yLook, zLook); + // float[] coordinates = new float[] { xPos, pos[1], pos[2], 1, xView, yView, zView, 1, xUp, yUp, zUp, 1 }; + + final float[] coordinates= new float[12]; + + coordinates[0] = pos[0]; + coordinates[1] = pos[1]; + coordinates[2] = pos[2]; + coordinates[3] = 1; + coordinates[4] = getxView(); + coordinates[5] = getyView(); + coordinates[6] = view[2]; + coordinates[7] = 1; + coordinates[8] = getxUp(); + coordinates[9] = getyUp(); + coordinates[10] = getzUp(); + coordinates[11] = 1; + + float[] newBuffer = new float[16]; + Math3DUtils.multiplyMMV(newBuffer, 0, buffer, 0, coordinates, 0); + + pos[0] = newBuffer[0]; + pos[1] = newBuffer[1]; + pos[2] = newBuffer[2]; + //view[0] = buffer[4]; + //view[1] = buffer[4 + 1]; + //view[2] = buffer[4 + 2]; + up[0] = newBuffer[8]; + up[1] = newBuffer[8 + 1]; + up[2] = newBuffer[8 + 2]; + + delegate.setChanged(true); + save(); + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/IsometricCamera.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/IsometricCamera.java new file mode 100644 index 00000000..a77cfd63 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/IsometricCamera.java @@ -0,0 +1,210 @@ +package org.andresoviedo.android_3d_model_engine.camera; + +import android.opengl.Matrix; +import android.util.Log; + +import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.android_3d_model_engine.model.Constants; +import org.andresoviedo.util.math.Math3DUtils; + +import java.util.Arrays; + +/** + * Isometric camera implementation that support rotation in all 3 axis + * This implementation supports a memory for saving and restoring the last location + * + * This implementation support all possible isometric positions, + * that is 8 (points) * 3 (directions) = 24 total + * + * Every position corresponds to 1 isometric key point (i.e. 1x,1y,1z) + * Every position is facing the world origin (0,0,0) + * + * For every rotation, the camera will land on the next isometric key point + * + */ +public class IsometricCamera extends Camera { + + /** + * The distance between the origin and the isometric coordinate + * This should be greater than the near view so it's not clipped + */ + public static final float UNIT = Constants.UNIT; // Constants.UNIT_SIN_3; + + private final Camera delegate; + private boolean initialized = false; + + private final float[] savePos; + private final float[] saveView; + private final float[] saveUp; + + public IsometricCamera(Camera delegate) { + super(delegate); + this.delegate = delegate; + + // final init + this.savePos = this.pos.clone(); + this.saveView = new float[]{0,0,0,1}; + this.saveUp = this.up.clone(); + } + + private boolean init() { + if (!initialized) { + this.savePos[0] = UNIT; + this.savePos[1] = UNIT; + this.savePos[2] = UNIT; + this.saveUp[0] = -Constants.UNIT_SIN_1; + this.saveUp[1] = Constants.UNIT_SIN_1; + this.saveUp[2] = -Constants.UNIT_SIN_1; + initialized = true; + return true; + } + return false; + } + + @Override + public void enable(){ + init(); + delegate.setDelegate(this); + saveAndAnimate(this.savePos[0], this.savePos[1], this.savePos[2], this.saveUp[0], this.saveUp[1], this.saveUp[2]); + } + + @Override + public synchronized void translateCamera(float dX, float dY) { + float dXabs2 = Math.abs(dX); + float dYabs2 = Math.abs(dY); + if (dXabs2 > dYabs2 && dX < 0) { + translateCameraIsometricRight(); + } else if (dXabs2 > dYabs2 && dX > 0) { + translateCameraIsometricLeft(); + } else if (dXabs2 < dYabs2 && dY > 0) { + translateCameraIsometricUp(); + } else if (dXabs2 < dYabs2 && dY < 0) { + translateCameraIsometricDown(); + } + } + + @Override + public synchronized void Rotate(float angle) { + final double rotationAngle = (angle > 0 ? -Math.PI : Math.PI) * 2 / 3; + final float[] posN = Math3DUtils.normalize2(savePos); + final float[] rotMatrix = new float[16]; + Math3DUtils.createRotationMatrixAroundVector(rotMatrix, 0, rotationAngle, posN[0], posN[1], posN[2]); + + float[] newUp = new float[4]; + Matrix.multiplyMV(newUp, 0, rotMatrix, 0, this.saveUp, 0); + Math3DUtils.normalize(newUp); + Math3DUtils.snapToGrid(newUp); + + //saveAndAnimate(getxPos(), getyPos(), getzPos(), newUp[0], newUp[1], newUp[2]); + saveAndAnimate(savePos[0], savePos[1], savePos[2], newUp[0], newUp[1], newUp[2]); + } + + private void translateCameraIsometricDown() { + // saveAndAnimate(-getxUp(), -getyUp(), -getzUp(), getxPos(), getyPos(), getzPos()); + float length = Math3DUtils.length(savePos); + saveAndAnimate(-saveUp[0] * UNIT, -saveUp[1] * UNIT, -saveUp[2] * UNIT, + savePos[0]/length, savePos[1]/length, savePos[2]/length); + } + + private void translateCameraIsometricUp() { + // saveAndAnimate(getxUp(), getyUp(), getzUp(), -getxPos(), -getyPos(), -getzPos()); + // saveAndAnimate(saveUp[0], saveUp[1], saveUp[2],-savePos[0], -savePos[1], -savePos[2]); + float length = Math3DUtils.length(savePos); + saveAndAnimate(saveUp[0] * UNIT, saveUp[1] * UNIT, saveUp[2] * UNIT, + -savePos[0]/length, -savePos[1]/length, -savePos[2]/length); + } + + private void translateCameraIsometricRight() { + //float[] right = Math3DUtils.crossProduct(getxUp(), getyUp(), getzUp(), -getxPos(), -getyPos(), -getzPos()); + float[] right = Math3DUtils.crossProduct(saveUp[0], saveUp[1], saveUp[2], -savePos[0], -savePos[1], -savePos[2]); + Math3DUtils.normalize(right); + Math3DUtils.snapToGrid(right); + rotateWithMatrix(right); + } + + private void translateCameraIsometricLeft() { + // float[] left = Math3DUtils.crossProduct(-getxPos(), -getyPos(), -getzPos(), getxUp(), getyUp(), getzUp()); + float[] left = Math3DUtils.crossProduct(-savePos[0], -savePos[1], -savePos[2], saveUp[0], saveUp[1], saveUp[2]); + Math3DUtils.normalize(left); + Math3DUtils.snapToGrid(left); + rotateWithMatrix(left); + } + + /** + * @param cross the perpendicular vector to the axis rotational vector + */ + private void rotateWithMatrix(float[] cross) { + final float dotProductX = Math3DUtils.dotProduct(cross, Math3DUtils.VECTOR_UNIT_X); + final float dotProductY = Math3DUtils.dotProduct(cross, Math3DUtils.VECTOR_UNIT_Y); + final float dotProductZ = Math3DUtils.dotProduct(cross, Math3DUtils.VECTOR_UNIT_Z); + + float[] axis; + if (Math.round(dotProductX) == 0f) { + axis = Math3DUtils.VECTOR_UNIT_X; + } else if (Math.round(dotProductY) == 0f) { + axis = Math3DUtils.VECTOR_UNIT_Y; + } else if (Math.round(dotProductZ) == 0f) { + axis = Math3DUtils.VECTOR_UNIT_Z; + } else { + // this should never happen + Log.w("IsometricCamera", "rotateWithMatrix() coding issue. ignoring action..."); + return; + } + + final float[] posN = Math3DUtils.normalize2(this.pos); + final float[] cross2 = Math3DUtils.crossProduct(posN, cross); + final double dot = Math3DUtils.dotProduct(axis, cross2); + + Log.v("IsometricCamera", "rotateWithMatrix. dot: " + dot + + ", axis: " + Arrays.toString(axis) + + ", cross: " + Arrays.toString(cross) + + ", angle: " + Math3DUtils.calculateAngleBetween(axis, cross)); + final double angle = Math.signum(dot) * Math.PI / 2; + + final float[] rotMatrix = new float[16]; + Math3DUtils.createRotationMatrixAroundVector(rotMatrix, 0, angle, axis); + + float[] newPos = new float[4]; + Matrix.multiplyMV(newPos, 0, rotMatrix, 0, this.pos, 0); + //Math3DUtils.normalize(newPos); + //Math3DUtils.mult(newPos, UNIT); + Math3DUtils.snapToGrid(newPos); + + float[] newUp = new float[4]; + Matrix.multiplyMV(newUp, 0, rotMatrix, 0, this.up, 0); + Math3DUtils.normalize(newUp); + Math3DUtils.snapToGrid(newUp); + + saveAndAnimate(newPos[0], newPos[1], newPos[2], newUp[0], newUp[1], newUp[2]); + + Log.v("IsometricCamera", "Rotating... action: " + delegate.getAnimation()); + } + + private void saveAndAnimate(float xp, float yp, float zp, float xu, float yu, float zu) { + this.saveAndAnimate(false, xp, yp, zp, xu, yu, zu); + } + + private void saveAndAnimate(boolean force, float xp, float yp, float zp, float xu, float yu, float zu) { + + synchronized (delegate) { + if (delegate.getAnimation() == null || delegate.getAnimation().isFinished() || force) { + + + savePos[0] = xp; + savePos[1] = yp; + savePos[2] = zp; + saveUp[0] = xu; + saveUp[1] = yu; + saveUp[2] = zu; + + /*delegate.setAnimation(new Object[]{"moveTo", getxPos(), getyPos(), getzPos(), getxUp(), getyUp(), getzUp(), + savePos[0], savePos[1], savePos[2], saveUp[0], saveUp[1], saveUp[2]});*/ + + Object[] args = new Object[]{"moveTo", getxPos(), getyPos(), getzPos(), getxUp(), getyUp(), getzUp(), + savePos[0], savePos[1], savePos[2], saveUp[0], saveUp[1], saveUp[2], + getxView(), getyView(), getzView(), saveView[0], saveView[1], saveView[2]}; + delegate.setAnimation(new CameraAnimation(delegate, args)); + } + } + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/OrthographicCamera.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/OrthographicCamera.java new file mode 100644 index 00000000..f1af6a55 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/OrthographicCamera.java @@ -0,0 +1,146 @@ +package org.andresoviedo.android_3d_model_engine.camera; + +import android.util.Log; + +import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.android_3d_model_engine.model.Constants; +import org.andresoviedo.util.math.Math3DUtils; + +import java.util.Arrays; + +public class OrthographicCamera extends Camera { + + /** + * The distance between the origin and the orthographic coordinate + * This should be greater than the near view so it's not clipped + */ + public static final float UNIT = Constants.UNIT * 2f; // Constants.UNIT_SIN_3; + + private final Camera delegate; + private boolean initialized = false; + + private final float[] savePos; + private final float[] saveView; + private final float[] saveUp; + + public OrthographicCamera(Camera delegate) { + super(delegate); + this.delegate = delegate; + + // final init + this.savePos = this.pos.clone(); + this.saveView = new float[]{0,0,0,1}; + this.saveUp = this.up.clone(); + } + + private boolean init() { + if (!initialized) { + this.savePos[0] = Constants.UNIT_0; + this.savePos[1] = Constants.UNIT_0; + this.savePos[2] = UNIT; + this.saveUp[0] = Constants.UNIT_0; + this.saveUp[1] = Constants.UNIT_1; + this.saveUp[2] = -Constants.UNIT_0; + initialized = true; + return true; + } + return false; + } + + @Override + public void enable() { + init(); + delegate.setDelegate(this); + saveAndAnimate(true, this.savePos[0], this.savePos[1], this.savePos[2], this.saveUp[0], this.saveUp[1], this.saveUp[2]); + } + + @Override + public synchronized void translateCamera(float dX, float dY) { + + float dXabs = Math.abs(dX); + float dYabs = Math.abs(dY); + if (dX < 0 && dXabs > dYabs) { // right + //float[] right = Math3DUtils.crossProduct(-getxPos(), -getyPos(), -getzPos(), getxUp(), getyUp(), getzUp()); + float[] right = Math3DUtils.crossProduct(-savePos[0], -savePos[1], -savePos[2], saveUp[0], saveUp[1], saveUp[2]); + Math3DUtils.normalize(right); + Math3DUtils.snapToGrid(right); + saveAndAnimate(right[0] * UNIT, right[1] * UNIT, right[2] * UNIT); + } else if (dX > 0 && dXabs > dYabs) { + // float[] left = Math3DUtils.crossProduct(getxUp(), getyUp(), getzUp(), -getxPos(), -getyPos(), -getzPos()); + float[] left = Math3DUtils.crossProduct(saveUp[0], saveUp[1], saveUp[2],-savePos[0], -savePos[1], -savePos[2]); + Math3DUtils.normalize(left); + Math3DUtils.snapToGrid(left); + saveAndAnimate(left[0] * UNIT, left[1] * UNIT, left[2] * UNIT); + } else if (dY > 0 && dYabs > dXabs) { + saveAndAnimate(saveUp[0] * UNIT, saveUp[1] * UNIT, saveUp[2] * UNIT); + } else if (dY < 0 && dYabs > dXabs) { + saveAndAnimate(-saveUp[0] * UNIT, -saveUp[1] * UNIT, -saveUp[2] * UNIT); + } + } + + @Override + public synchronized void Rotate(float angle) { + + if (angle < 0) { + float[] right = Math3DUtils.crossProduct(-savePos[0], -savePos[1], -savePos[2], saveUp[0], saveUp[1], saveUp[2]); + Math3DUtils.normalize(right); + Math3DUtils.snapToGrid(right); + Log.v("OrthographicCamera", "Rotating 90 right: " + Arrays.toString(right)); + saveAndAnimate(savePos[0], savePos[1], savePos[2], right[0], right[1], right[2]); + } else { + float[] left = Math3DUtils.crossProduct(saveUp[0], saveUp[1], saveUp[2], -savePos[0], -savePos[1], -savePos[2]); + Math3DUtils.normalize(left); + Math3DUtils.snapToGrid(left); + Log.v("OrthographicCamera", "Rotating 90 left: " + Arrays.toString(left)); + saveAndAnimate(savePos[0], savePos[1], savePos[2], left[0], left[1], left[2]); + } + } + + private void saveAndAnimate(float xp, float yp, float zp) { + + // UP vector must be recalculated + // cross + float[] right = Math3DUtils.crossProduct(-savePos[0], -savePos[1], -savePos[2], + saveUp[0], saveUp[1], saveUp[2]); + Math3DUtils.normalize(right); + + float[] cross = Math3DUtils.crossProduct(right[0], right[1], right[2], -xp, -yp, -zp); + if (Math3DUtils.length(cross) > 0f){ + Math3DUtils.normalize(cross); + Math3DUtils.snapToGrid(cross); + saveAndAnimate(xp,yp,zp, cross[0], cross[1], cross[2]); + } else { + saveAndAnimate(xp,yp,zp, saveUp[0], saveUp[1], saveUp[2]); + } + } + + private void saveAndAnimate(float xp, float yp, float zp, float xu, float yu, float zu) { + this.saveAndAnimate(false, xp, yp, zp, xu, yu, zu); + } + + private void saveAndAnimate(boolean force, float xp, float yp, float zp, float xu, float yu, float zu) { + + synchronized (delegate) { + if (delegate.getAnimation() == null || delegate.getAnimation().isFinished() || force) { + + + + + /*delegate.setAnimation(new Object[]{"moveTo", getxPos(), getyPos(), getzPos(), getxUp(), getyUp(), getzUp(), + savePos[0], savePos[1], savePos[2], saveUp[0], saveUp[1], saveUp[2]});*/ + Object[] args = new Object[]{"moveTo", getxPos(), getyPos(), getzPos(), getxUp(), getyUp(), getzUp(), + xp, yp, zp, xu, yu, zu, getxView(), getyView(), getzView(), saveView[0], saveView[1], saveView[2]}; + + savePos[0] = xp; + savePos[1] = yp; + savePos[2] = zp; + saveUp[0] = xu; + saveUp[1] = yu; + saveUp[2] = zu; + + delegate.setAnimation(new CameraAnimation(delegate, args)); + } + + } + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/PointOfViewCamera.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/PointOfViewCamera.java new file mode 100644 index 00000000..8d8434e7 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/camera/PointOfViewCamera.java @@ -0,0 +1,166 @@ +package org.andresoviedo.android_3d_model_engine.camera; + +import android.opengl.Matrix; + +import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.util.math.Math3DUtils; + +public class PointOfViewCamera extends Camera { + + private final Camera delegate; + + private final float[] savePos; + private final float[] saveUp; + private final float[] saveView; + + public PointOfViewCamera(Camera delegate) { + super(delegate); + this.delegate = delegate; + this.savePos = this.pos.clone(); + this.saveUp = this.up.clone(); + this.saveView = this.view.clone(); + } + + @Override + public void enable(){ + delegate.setDelegate(this); + saveAndAnimate(this.savePos[0], this.savePos[1], this.savePos[2], this.saveUp[0], this.saveUp[1], this.saveUp[2], + this.saveView[0], this.saveView[1], this.saveView[2]); + } + + private void save(){ + System.arraycopy(this.pos, 0, this.savePos, 0, this.pos.length); + System.arraycopy(this.view, 0, this.saveView, 0, this.view.length); + System.arraycopy(this.up, 0, this.saveUp, 0, this.up.length); + } + + @Override + public void translateCamera(float dX, float dY) { + + if (dX == 0 && dY == 0) return; + + // get current view and right + float[] view = Math3DUtils.to4d(Math3DUtils.substract(this.view, this.pos)); + float[] right = Math3DUtils.to4d(Math3DUtils.crossProduct(view, this.up)); + if (Math3DUtils.length(right) == 0) return; + + Math3DUtils.normalize(right); + + // add deltas + float[] rightd = Math3DUtils.multiply(right, dY); + float[] upd = Math3DUtils.multiply(up, dX); + + // rot vectors + float[] viewRot = Math3DUtils.add(rightd,upd); + float length = Math3DUtils.length(viewRot); + Math3DUtils.normalize(viewRot); + + // transform + float[] matrixView = new float[16]; + Matrix.setIdentityM(matrixView,0); + Matrix.translateM(matrixView,0, getxPos(), getyPos(), getzPos()); + Matrix.rotateM(matrixView, 0, -(float) Math.toDegrees(length), viewRot[0], viewRot[1], viewRot[2]); + + final float[] newView = new float[4]; + Matrix.multiplyMV(newView,0,matrixView,0, view,0); + this.view[0] = newView[0]; + this.view[1] = newView[1]; + this.view[2] = newView[2]; + + // ------------------------ + + float[] matrixUp = new float[16]; + Matrix.setIdentityM(matrixUp,0); + Matrix.rotateM(matrixUp, 0, -(float) Math.toDegrees(length), viewRot[0], viewRot[1], viewRot[2]); + + float[] newUp = new float[4]; + Matrix.multiplyMV(newUp,0,matrixUp,0, this.up,0); + Math3DUtils.normalize(newUp); + + this.up[0] = newUp[0]; + this.up[1] = newUp[1]; + this.up[2] = newUp[2]; + + delegate.setChanged(true); + } + + public synchronized void MoveCameraZ(float direction) { + + // First we need to get the direction at which we are looking. + float xLookDirection, yLookDirection, zLookDirection; + + // The look direction is the view minus the position (where we are). + xLookDirection = getxView() - pos[0]; + yLookDirection = getyView() - pos[1]; + zLookDirection = view[2] - pos[2]; + + // Normalize the direction. + float dp = Matrix.length(xLookDirection, yLookDirection, zLookDirection); + xLookDirection /= dp; + yLookDirection /= dp; + zLookDirection /= dp; + + float x = pos[0] + xLookDirection * direction; + float y = pos[1] + yLookDirection * direction; + float z = pos[2] + zLookDirection * direction; + + if (isOutOfBounds(x, y, z)) return; + + pos[0] = x; + pos[1] = y; + pos[2] = z; + + view[0] += xLookDirection * direction; + view[1] += yLookDirection * direction; + view[2] += zLookDirection * direction; + + save(); + + delegate.setChanged(true); + } + + @Override + public void Rotate(float angle) { + + if (angle == 0) return; + + // get current view and right + float[] view = Math3DUtils.to4d(Math3DUtils.substract(this.view, this.pos)); + Math3DUtils.normalize(view); + + // transform + float[] matrix = new float[16]; + Matrix.setRotateM(matrix, 0, (float) -Math.toDegrees(angle), view[0], view[1], view[2]); + + final float[] newUp = new float[4]; + Matrix.multiplyMV(newUp,0,matrix,0, this.up,0); + this.up[0] = newUp[0]; + this.up[1] = newUp[1]; + this.up[2] = newUp[2]; + + save(); + + delegate.setChanged(true); + } + + private void saveAndAnimate(float xp, float yp, float zp, + float xu, float yu, float zu, + float xv, float yv, float zv) { + + Object[] args = new Object[]{"moveTo", getxPos(), getyPos(), getzPos(), getxUp(), getyUp(), getzUp(), + xp, yp, zp, xu, yu, zu, getxView(), getyView(), getzView(), xv, yv, zv}; + + savePos[0] = xp; + savePos[1] = yp; + savePos[2] = zp; + saveUp[0] = xu; + saveUp[1] = yu; + saveUp[2] = zu; + saveView[0] = xv; + saveView[1] = yv; + saveView[2] = zv; + + delegate.setAnimation(new CameraAnimation(delegate, args)); + + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionController.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionController.java index 2fd4ee94..c27853ce 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionController.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionController.java @@ -16,6 +16,12 @@ import java.util.EventObject; import java.util.List; +/** + * Collision controller that, based on View settings (width, height and projection matrices) + * it can detect a collision between an Object and a Ray casted from the screen to the farthest point + * + * Collision controller processes {@link TouchEvent} and fires {@link CollisionEvent} + */ public class CollisionController implements EventListener { private final ModelSurfaceView view; @@ -64,17 +70,18 @@ public boolean onEvent(EventObject event) { (), view.getViewMatrix(), view.getProjectionMatrix(), x, y); if (point != null) { - Log.i("CollisionController", "Drawing intersection point: " + Arrays.toString(point)); + Log.i("CollisionController", "Building intersection point: " + Arrays.toString(point)); point3D = Point.build(point).setColor(new float[]{1.0f, 0f, 0f, 1f}); } } final CollisionEvent collisionEvent = new CollisionEvent(this, objectHit, x, y, point3D); AndroidUtils.fireEvent(listeners, collisionEvent); + return true; } } } - return true; + return false; } } \ No newline at end of file diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionDetection.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionDetection.java index caf5ac2c..b25f8409 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionDetection.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/CollisionDetection.java @@ -1,6 +1,7 @@ package org.andresoviedo.android_3d_model_engine.collision; import android.opengl.GLU; +import android.opengl.Matrix; import android.util.Log; import org.andresoviedo.android_3d_model_engine.model.BoundingBox; @@ -34,33 +35,43 @@ public static Object3DData getBoxIntersection(List objects, int wi float[] farHit = unProject(width, height, modelViewMatrix, modelProjectionMatrix, windowX, windowY, 1); float[] direction = Math3DUtils.substract(farHit, nearHit); Math3DUtils.normalize(direction); - return getBoxIntersection(objects, nearHit, direction); + return getBoxIntersection(objects, nearHit, farHit, direction); } /** * Get the nearest object intersected by the specified ray or null if no object is intersected * * @param objects the list of objects to test - * @param p1 the ray start point + * @param nearHit the ray start point + * @param farHit the ray far hit * @param direction the ray direction * @return the object intersected by the specified ray */ - private static Object3DData getBoxIntersection(List objects, float[] p1, float[] direction) { + private static Object3DData getBoxIntersection(List objects, float[] nearHit, float[] farHit, float[] direction) { float min = Float.MAX_VALUE; Object3DData ret = null; for (Object3DData obj : objects) { if ("Point".equals(obj.getId()) || "Line".equals(obj.getId())) { continue; } - BoundingBox box = obj.getBoundingBox(); - float[] intersection = getBoxIntersection(p1, direction, box); + + float[] invertedModelMatrix = new float[16]; + Matrix.invertM(invertedModelMatrix,0, obj.getModelMatrix(),0); + float[] nearAA = new float[4]; + float[] farAA = new float[4]; + Matrix.multiplyMV(nearAA,0,invertedModelMatrix,0,nearHit,0); + Matrix.multiplyMV(farAA,0,invertedModelMatrix,0,farHit,0); + float[] dirAA = Math3DUtils.substract(farAA,nearAA); + Math3DUtils.normalize(dirAA); + + float[] intersection = getBoxIntersection(nearAA, dirAA, obj.getBoundingBox()); if (intersection[0] > 0 && intersection[0] <= intersection[1] && intersection[0] < min) { min = intersection[0]; ret = obj; } } if (ret != null) { - Log.i("CollisionDetection", "Collision detected '" + ret.getId() + "' distance: " + min + ", dimensions: " + ret.getCurrentDimensions()); + Log.i("CollisionDetection", "Collision detected '" + ret.getId() + "' distance: " + min); } return ret; } @@ -105,7 +116,7 @@ private static Object3DData getBoxIntersection(List objects, float */ private static boolean isBoxIntersection(float[] origin, float[] dir, BoundingBox b) { float[] intersection = getBoxIntersection(origin, dir, b); - return intersection[0] > 0 && intersection[0] < intersection[1]; + return intersection[0] >= 0 && intersection[0] <= intersection[1]; } /** @@ -196,9 +207,9 @@ public static float[] getTriangleIntersection(List objects, int wi float[] farHit = unProject(width, height, modelViewMatrix, modelProjectionMatrix, windowX, windowY, 1); float[] direction = Math3DUtils.substract(farHit, nearHit); Math3DUtils.normalize(direction); - Object3DData intersected = getBoxIntersection(objects, nearHit, direction); + Object3DData intersected = getBoxIntersection(objects, nearHit, farHit, direction); if (intersected != null) { - return getTriangleIntersection(intersected, nearHit, direction); + return getTriangleIntersection(intersected, nearHit, farHit, direction); } return null; } @@ -208,11 +219,12 @@ public static float[] getTriangleIntersection(Object3DData hit, int width, int h float[] farHit = unProject(width, height, viewMatrix, projectionMatrix, windowX, windowY, 1); float[] direction = Math3DUtils.substract(farHit, nearHit); Math3DUtils.normalize(direction); - return getTriangleIntersection(hit, nearHit, direction); + return getTriangleIntersection(hit, nearHit, farHit, direction); } - private static float[] getTriangleIntersection(final Object3DData hit, float[] nearHit, float[] direction) { + private static float[] getTriangleIntersection(final Object3DData hit, float[] nearHit, float[] farHit, float[] direction) { Log.d("CollisionDetection", "Getting triangle intersection: " + hit.getId()); + Octree octree; synchronized (hit) { octree = hit.getOctree(); @@ -221,11 +233,24 @@ private static float[] getTriangleIntersection(final Object3DData hit, float[] n hit.setOctree(octree); } } - float intersection = getTriangleIntersectionForOctree(octree, nearHit, direction); + + // invert ray + float[] inverted = new float[16]; + Matrix.invertM(inverted,0, hit.getModelMatrix(),0); + float[] nearAA = new float[4]; + float[] farAA = new float[4]; + Matrix.multiplyMV(nearAA,0,inverted,0,nearHit,0); + Matrix.multiplyMV(farAA,0,inverted,0,farHit,0); + float[] dirAA = Math3DUtils.substract(farAA,nearAA); + Math3DUtils.normalize(dirAA); + + float intersection = getTriangleIntersectionForOctree(octree, nearAA, dirAA); if (intersection != -1) { - float[] intersectionPoint = Math3DUtils.add(nearHit, Math3DUtils.multiply(direction, intersection)); - Log.d("CollisionDetection", "Interaction point: " + Arrays.toString(intersectionPoint)); - return intersectionPoint; + float[] intersectionPoint = Math3DUtils.add(nearAA, Math3DUtils.multiply(dirAA, intersection)); + float[] realIntersection = new float[4]; + Matrix.multiplyMV(realIntersection,0, hit.getModelMatrix(),0,Math3DUtils.to4d(intersectionPoint),0); + Log.d("CollisionDetection", "Collision point: " + Arrays.toString(realIntersection)); + return realIntersection; } else { return null; } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/Octree.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/Octree.java index 2557bbb0..a2c71b74 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/Octree.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/collision/Octree.java @@ -63,16 +63,17 @@ static Octree build(Object3DData object){ // vertex array contains vertex in sequence final FloatBuffer buffer = object.getVertexBuffer().asReadOnlyBuffer(); final List triangles = new ArrayList<>(buffer.capacity() / 3 * 4); - final float[] modelMatrix = object.getModelMatrix(); + //final float[] modelMatrix = object.getModelMatrix(); + //final float[] modelMatrix = Math3DUtils.IDENTITY_MATRIX; buffer.position(0); for (int i = 0; i < buffer.capacity(); i += 9) { float[] triangle = new float[]{buffer.get(), buffer.get(), buffer.get(), 1, buffer.get(), buffer.get(), buffer.get(), 1, buffer.get(), buffer.get(), buffer.get(), 1 }; - Matrix.multiplyMV(triangle, 0, modelMatrix, 0, triangle, 0); - Matrix.multiplyMV(triangle, 4, modelMatrix, 0, triangle, 4); - Matrix.multiplyMV(triangle, 8, modelMatrix, 0, triangle, 8); + //Matrix.multiplyMV(triangle, 0, modelMatrix, 0, triangle, 0); + //Matrix.multiplyMV(triangle, 4, modelMatrix, 0, triangle, 4); + //Matrix.multiplyMV(triangle, 8, modelMatrix, 0, triangle, 8); triangles.add(triangle); } ret.pending.addAll(triangles); @@ -81,16 +82,17 @@ static Octree build(Object3DData object){ final IntBuffer drawOrder = object.getDrawOrder().asReadOnlyBuffer(); final FloatBuffer buffer = object.getVertexBuffer().asReadOnlyBuffer(); final List triangles = new ArrayList<>(drawOrder.capacity() / 3 * 4); - final float[] modelMatrix = object.getModelMatrix(); + //final float[] modelMatrix = object.getModelMatrix(); + //final float[] modelMatrix = Math3DUtils.IDENTITY_MATRIX; for (int i = 0; i < drawOrder.capacity(); i += 3) { float[] triangle = new float[]{ buffer.get(drawOrder.get(i)), buffer.get(drawOrder.get(i)+1), buffer.get(drawOrder.get(i)+2), 1, buffer.get(drawOrder.get(i+1)), buffer.get(drawOrder.get(i+1)+1), buffer.get(drawOrder.get(i+1)+2), 1, buffer.get(drawOrder.get(i+2)), buffer.get(drawOrder.get(i+2)+1), buffer.get(drawOrder.get(i+2)+2), 1, }; - Matrix.multiplyMV(triangle, 0, modelMatrix, 0, triangle, 0); - Matrix.multiplyMV(triangle, 4, modelMatrix, 0, triangle, 4); - Matrix.multiplyMV(triangle, 8, modelMatrix, 0, triangle, 8); + //Matrix.multiplyMV(triangle, 0, modelMatrix, 0, triangle, 0); + //Matrix.multiplyMV(triangle, 4, modelMatrix, 0, triangle, 4); + //Matrix.multiplyMV(triangle, 8, modelMatrix, 0, triangle, 8); triangles.add(triangle); } ret.pending.addAll(triangles); diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchController.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchController.java index 9bebc8cd..57c72782 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchController.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchController.java @@ -7,16 +7,18 @@ import android.view.MotionEvent; import android.widget.Toast; +import org.andresoviedo.android_3d_model_engine.view.ViewEvent; import org.andresoviedo.util.android.AndroidUtils; import org.andresoviedo.util.event.EventListener; +import org.andresoviedo.util.math.Math3DUtils; import java.util.ArrayList; import java.util.EventObject; import java.util.List; -public class TouchController { +public class TouchController implements EventListener { - private static final String TAG = TouchController.class.getName(); + private static final String TAG = TouchController.class.getSimpleName(); private static final int TOUCH_STATUS_ZOOMING_CAMERA = 1; private static final int TOUCH_STATUS_ROTATING_CAMERA = 4; @@ -63,16 +65,15 @@ public class TouchController { private float previousY2; private float[] previousVector = new float[4]; private float[] vector = new float[4]; - private float[] rotationVector = new float[4]; private float previousRotationSquare; public TouchController(Activity parent) { super(); try { if (!AndroidUtils.supportsMultiTouch(parent.getPackageManager())) { - Log.w("ModelActivity","Multitouch not supported. Some app features may not be available"); + Log.w(TAG,"Multitouch not supported. Some app features may not be available"); } else { - Log.i("ModelActivity","Initializing TouchController..."); + Log.i(TAG,"Initializing TouchController..."); } }catch (Exception e){ Toast.makeText(parent, "Error loading Touch Controller:\n" +e.getMessage(), Toast.LENGTH_LONG).show(); @@ -92,9 +93,9 @@ private void fireEvent(EventObject eventObject){ AndroidUtils.fireEvent(listeners, eventObject); } - public boolean onTouchEvent(MotionEvent motionEvent) { + public boolean onMotionEvent(MotionEvent motionEvent) { - Log.v("TouchController","Processing MotionEvent..."); + Log.v(TAG,"Processing MotionEvent..."); final int pointerCount = motionEvent.getPointerCount(); switch (motionEvent.getActionMasked()) { @@ -173,14 +174,6 @@ public boolean onTouchEvent(MotionEvent motionEvent) { dx2 = x2 - previousX2; dy2 = y2 - previousY2; - rotationVector[0] = (previousVector[1] * vector[2]) - (previousVector[2] * vector[1]); - rotationVector[1] = (previousVector[2] * vector[0]) - (previousVector[0] * vector[2]); - rotationVector[2] = (previousVector[0] * vector[1]) - (previousVector[1] * vector[0]); - len = Matrix.length(rotationVector[0], rotationVector[1], rotationVector[2]); - rotationVector[0] /= len; - rotationVector[1] /= len; - rotationVector[2] /= len; - previousLength = (float) Math .sqrt(Math.pow(previousX2 - previousX1, 2) + Math.pow(previousY2 - previousY1, 2)); length = (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); @@ -198,35 +191,37 @@ public boolean onTouchEvent(MotionEvent motionEvent) { // gesture detection isOneFixedAndOneMoving = ((dx1 + dy1) == 0) != (((dx2 + dy2) == 0)); - fingersAreClosing = !isOneFixedAndOneMoving && (Math.abs(dx1 + dx2) < 10 && Math.abs(dy1 + dy2) < 10); - isRotating = !isOneFixedAndOneMoving && (dx1 != 0 && dy1 != 0 && dx2 != 0 && dy2 != 0) - && rotationVector[2] != 0; + fingersAreClosing = (Math.abs(dx1 + dx2) < 10 && Math.abs(dy1 + dy2) < 10); + isRotating = !isOneFixedAndOneMoving && (dx1 != 0 && dy1 != 0 && dx2 != 0 && dy2 != 0); } if (pointerCount == 1 && simpleTouch) { fireEvent(new TouchEvent(this, TouchEvent.CLICK, width, height, x1, y1)); - } - - if (touchDelay > 1) { - // INFO: Process gesture - if (pointerCount == 1 && currentPress1 > 4.0f) { - } else if (pointerCount == 1) { - fireEvent(new TouchEvent(this, TouchEvent.MOVE, width, height, previousX1, previousY1, - x1, y1, dx1, dy1, 0, - null)); - touchStatus = TOUCH_STATUS_MOVING_WORLD; - } else if (pointerCount == 2) { + } else { + //if (touchDelay > 1) { + // INFO: Process gesture + /*if (pointerCount == 1 && currentPress1 > 4.0f) { + } else */if (pointerCount == 1) { + fireEvent(new TouchEvent(this, TouchEvent.MOVE, width, height, previousX1, previousY1, + x1, y1, dx1, dy1, 0, + 0f)); + touchStatus = TOUCH_STATUS_MOVING_WORLD; + } else if (pointerCount == 2) { if (fingersAreClosing) { - fireEvent(new TouchEvent(this, TouchEvent.PINCH, width, height, previousX1, previousY1, - x1, y1, dx1, dy1, (length - previousLength), null)); - touchStatus = TOUCH_STATUS_ZOOMING_CAMERA; + fireEvent(new TouchEvent(this, TouchEvent.PINCH, width, height, previousX1, previousY1, + x1, y1, dx1, dy1, (previousLength - length), 0f)); + touchStatus = TOUCH_STATUS_ZOOMING_CAMERA; + fingersAreClosing = false; + } else if (isRotating) { + final double angle = Math3DUtils.calculateAngleBetween(previousVector, vector); + if (Math.abs(angle) >= Math.PI / 180) { + fireEvent(new TouchEvent(this, TouchEvent.ROTATE, width, height, previousX1, previousY1 + , x1, y1, dx1, dy1, 0, (float) angle)); + touchStatus = TOUCH_STATUS_ROTATING_CAMERA; + } + } } - if (isRotating) { - fireEvent(new TouchEvent(this, TouchEvent.ROTATE, width, height, previousX1, previousY1 - , x1, y1, dx1, dy1, 0, rotationVector)); - touchStatus = TOUCH_STATUS_ROTATING_CAMERA; - } - } + //} } previousX1 = x1; @@ -240,7 +235,21 @@ public boolean onTouchEvent(MotionEvent motionEvent) { if (gestureChanged && touchDelay > 1) { gestureChanged = false; - Log.v(TAG, "Fin"); + } + return true; + } + + @Override + public boolean onEvent(EventObject event) { + Log.v("TouchController","Processing event... "+ event); + if (event.getSource() instanceof MotionEvent){ + return onMotionEvent((MotionEvent) event.getSource()); + } + else if (event instanceof ViewEvent) { + ViewEvent viewEvent = (ViewEvent) event; + if (viewEvent.getCode() == ViewEvent.Code.SURFACE_CHANGED) { + this.setSize(viewEvent.getWidth(), viewEvent.getHeight()); + } } return true; } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchEvent.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchEvent.java index 032c9221..69bf9e5f 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchEvent.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/controller/TouchEvent.java @@ -22,15 +22,15 @@ public enum Action {CLICK, MOVE, PINCH, ROTATE, SPREAD} private final float dX; private final float dY; private final float zoom; - private final float[] rotation; + private final float angle; TouchEvent(Object source, Action action, int width, int height, float x, float y) { - this(source, action, width, height, x, y, 0, 0, 0, 0, 0, null); + this(source, action, width, height, x, y, 0, 0, 0, 0, 0, 0f); } TouchEvent(Object source, Action action, int width, int height, float x, float y, float x2, float y2, float dX, - float dY, float zoom, float[] rotation) { + float dY, float zoom, float angle) { super(source); this.width = width; this.height = height; @@ -42,7 +42,7 @@ public enum Action {CLICK, MOVE, PINCH, ROTATE, SPREAD} this.x2 = x2; this.y2 = y2; this.zoom = zoom; - this.rotation = rotation; + this.angle = angle; } public int getWidth() { @@ -86,8 +86,12 @@ public float getZoom() { return zoom; } - public float[] getRotation() { - return rotation; + public float getAngle() { + return angle; + } + + public float getLength() { + return (float) Math.sqrt(dX * dX + dY * dY); } @Override diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/event/SelectedObjectEvent.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/event/SelectedObjectEvent.java new file mode 100644 index 00000000..3b6d7aac --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/event/SelectedObjectEvent.java @@ -0,0 +1,19 @@ +package org.andresoviedo.android_3d_model_engine.event; + +import org.andresoviedo.android_3d_model_engine.model.Object3DData; + +import java.util.EventObject; + +public class SelectedObjectEvent extends EventObject { + + private final Object3DData selected; + + public SelectedObjectEvent(Object source, Object3DData selected) { + super(source); + this.selected = selected; + } + + public Object3DData getSelected() { + return selected; + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/GUI.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/GUI.java index 5831699c..5eeeda34 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/GUI.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/GUI.java @@ -18,78 +18,11 @@ public class GUI extends Widget implements EventListener { - public static final int POSITION_TOP_LEFT = 0; - public static final int POSITION_MIDDLE = 4; - public static final int POSITION_TOP_RIGHT = 2; - - private final float[] cameraPosition = new float[]{0, 0, 1.1f}; - private final float[] projectionMatrix = new float[16]; - private final float[] viewMatrix = new float[16]; - - private int width; - private int height; - private float ratio; - public GUI(){ super(); addListener(this); } - /** - * @param width horizontal screen pixels - * @param height vertical screen pixels - */ - public void setSize(int width, int height) { - this.width = width; - this.height = height; - this.ratio = (float) width / height; - - // setup view & projection - Matrix.setLookAtM(viewMatrix, 0, - cameraPosition[0], cameraPosition[1], cameraPosition[2], - 0, 0, 0, - 0, 1, 0); - Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, 1, 10); - - FloatBuffer vertexBuffer = IOUtils.createFloatBuffer(5 * 3); - vertexBuffer.put(-ratio).put(-1).put(-1); - vertexBuffer.put(+ratio).put(-1).put(-1); - vertexBuffer.put(+ratio).put(1).put(-1); - vertexBuffer.put(-ratio).put(1).put(-1); - vertexBuffer.put(-ratio).put(-1).put(-1); - setVertexBuffer(vertexBuffer); - - setVisible(true); - - } - - // set position based on layout: - // top-left, top-middle, top-right = 0 1 2 - // middle-left, middle, middle-right = 3 4 5 - // bottom-left, bottom, bottom-right = 6 7 8 - public void addWidget(Widget widget) { - super.addWidget(widget); - widget.setRatio(ratio); - Log.i("GUI","Widget added: "+widget); - } - - public void render(RendererFactory rendererFactory, float[] lightPosInWorldSpace, float[] colorMask) { - super.render(rendererFactory, lightPosInWorldSpace, colorMask); - for (int i = 0; i < widgets.size(); i++) { - renderWidget(rendererFactory, widgets.get(i), lightPosInWorldSpace, colorMask); - } - } - - private void renderWidget(RendererFactory rendererFactory, Widget widget, float[] lightPosInWorldSpace, float[] - colorMask) { - if (!widget.isVisible()) return; - widget.onDrawFrame(); - Renderer drawer = rendererFactory.getDrawer(widget, false, false, false, false, true); - - GLES20.glLineWidth(2.0f); - drawer.draw(widget, projectionMatrix, viewMatrix, -1, lightPosInWorldSpace, colorMask, cameraPosition); - } - @Override public boolean onEvent(EventObject event) { super.onEvent(event); @@ -118,7 +51,7 @@ private void processMove(TouchEvent touchEvent) { for (int i = 0; i < this.widgets.size(); i++) { if (!this.widgets.get(i).isVisible() || !this.widgets.get(i).isSolid()) continue; - float[] intersection = CollisionDetection.getBoxIntersection(nearHit, direction, this.widgets.get(i).getBoundingBox()); + float[] intersection = CollisionDetection.getBoxIntersection(nearHit, direction, this.widgets.get(i).getCurrentBoundingBox()); if (intersection[0] >= 0 && intersection[0] <= intersection[1]) { Widget widget = this.widgets.get(i); Log.i("GUI", "Click! " + widget.getId()); @@ -151,7 +84,7 @@ private void processClick(float x, float y) { for (int i = 0; i < this.widgets.size(); i++) { if (!this.widgets.get(i).isVisible() || !this.widgets.get(i).isSolid()) continue; - float[] intersection = CollisionDetection.getBoxIntersection(nearHit, direction, this.widgets.get(i).getBoundingBox()); + float[] intersection = CollisionDetection.getBoxIntersection(nearHit, direction, this.widgets.get(i).getCurrentBoundingBox()); if (intersection[0] >= 0 && intersection[0] <= intersection[1]) { Widget widget = this.widgets.get(i); Log.i("GUI", "Click! " + widget.getId()); diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Text.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Text.java index 638331ac..4f553ee4 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Text.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Text.java @@ -9,6 +9,13 @@ import java.util.HashMap; import java.util.Map; +/** + * 8 bit font + * + * Based on 5 x 7 monospaced pixel font + * + * All Glyphs are drawn using a line strip + */ public final class Text extends Widget { private final static float[] SYMBOL_MINUS = new float[]{ @@ -16,6 +23,29 @@ public final class Text extends Widget { 0.5f,0.3f,0f, }; + private final static float[] SYMBOL_POINT = new float[]{ + 0.1f,0.1f,0f, + 0.1f,0.2f,0f, + 0.2f,0.2f,0f, + 0.2f,0.1f,0f, + 0.1f,0.1f,0f, + }; + + private final static float[] SYMBOL_COMMA = new float[]{ + 0.1f,0.0f,0f, + 0.2f,0.1f,0f, + 0.2f,0.2f,0f, + }; + + private final static float[] SYMBOL_COLON = new float[]{ + 0.1f,0.0f,0f, + 0.1f,0.2f,0f, + 0.1f,0.2f,0f, + 0.1f,0.3f,0f, + 0.1f,0.3f,0f, + 0.1f,0.5f,0f + }; + private final static float[] _0 = new float[]{ 0f,0.2f,0f, 0f,0.1f,0f, @@ -50,10 +80,35 @@ public final class Text extends Widget { 0.1f,0f,0f, 0.3f,0f,0f, 0.2f,0f,0f, + 0.2f,0.3f,0f, + 0.1f,0.3f,0f, + 0.1f,0.3f,0f, + 0.2f,0.4f,0f, 0.2f,0.4f,0f, - 0.1f,0.3f,0f + 0.2f,0.5f,0f, + }; + private final static float[] LETTER_j = new float[]{ + 0.1f,0.1f,0f, + 0.2f,0.0f,0f, + 0.3f,0.1f,0f, + 0.3f,0.4f,0f, + 0.3f,0.4f,0f, + 0.3f,0.5f,0f, + 0.3f,0.5f,0f, + 0.3f,0.6f,0f, }; + private final static float[] LETTER_k = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.6f,0f, + 0.0f,0.2f,0f, + 0.1f,0.2f,0f, + 0.3f,0.4f,0f, + 0.1f,0.2f,0f, + 0.3f,0.0f,0f, + }; + + private final static float[] LETTER_m = new float[]{ 0.0f,0.0f,0f, 0.0f,0.4f,0f, @@ -160,6 +215,7 @@ public final class Text extends Widget { }; private final static float[] LETTER_c = new float[]{ + 0.4f,0.3f,0f, 0.3f,0.4f,0f, 0.1f,0.4f,0f, 0.0f,0.3f,0f, @@ -178,7 +234,8 @@ public final class Text extends Widget { 0.0f,0.3f,0f, 0.0f,0.1f,0f, 0.1f,0.0f,0f, - 0.4f,0.0f,0f, + 0.2f,0.0f,0f, + 0.4f,0.2f,0f, }; private final static float[] f = new float[]{ @@ -202,8 +259,17 @@ public final class Text extends Widget { 0.0f,0.2f,0f, }; + private final static float[] LETTER_q = new float[]{ + 0.4f,0.0f,0f, + 0.4f,0.4f,0f, + 0.1f,0.4f,0f, + 0.0f,0.3f,0f, + 0.1f,0.2f,0f, + 0.4f,0.2f,0f, + }; + private final static float[] s = new float[]{ - 0.3f,0.4f,0f, + 0.4f,0.4f,0f, 0.1f,0.4f,0f, 0.0f,0.3f,0f, 0.1f,0.2f,0f, @@ -226,7 +292,7 @@ public final class Text extends Widget { }; private final static float[] LETTER_e = new float[]{ - 0.3f,0.0f,0f, + 0.4f,0.0f,0f, 0.1f,0.0f,0f, 0.0f,0.1f,0f, 0.0f,0.3f,0f, @@ -234,7 +300,7 @@ public final class Text extends Widget { 0.3f,0.4f,0f, 0.4f,0.3f,0f, 0.4f,0.2f,0f, - 0.0f,0.2f,0f, + 0.0f,0.2f,0f }; private final static float[] LETTER_g = new float[]{ @@ -288,6 +354,52 @@ public final class Text extends Widget { 0.0f,0.0f,0f, }; + + private final static float[] LETTER_y = new float[]{ + 0.0f,0.4f,0f, + 0.0f,0.3f,0f, + 0.1f,0.2f,0f, + 0.4f,0.2f,0f, + 0.4f,0.4f,0f, + 0.4f,0.1f,0f, + 0.3f,0.0f,0f, + 0.1f,0.0f,0f, + }; + + private final static float[] LETTER_z = new float[]{ + 0.0f,0.4f,0f, + 0.4f,0.4f,0f, + 0.0f,0.0f,0f, + 0.4f,0.0f,0f + }; + + private final static float[] LETTER_A = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.5f,0f, + 0.1f,0.6f,0f, + 0.3f,0.6f,0f, + 0.4f,0.5f,0f, + 0.4f,0.0f,0f, + 0.4f,0.2f,0f, + 0.0f,0.2f,0f + }; + + private final static float[] LETTER_B = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.6f,0f, + 0.3f,0.6f,0f, + 0.4f,0.5f,0f, + 0.4f,0.4f,0f, + 0.3f,0.3f,0f, + 0.0f,0.3f,0f, + 0.3f,0.3f,0f, + 0.4f,0.2f,0f, + 0.4f,0.1f,0f, + 0.3f,0.0f,0f, + 0.0f,0.0f,0f, + + }; + private final static float[] LETTER_C = new float[]{ 0.4f,0.5f,0f, 0.3f,0.6f,0f, @@ -306,6 +418,24 @@ public final class Text extends Widget { 0.4f,0.0f,0f, }; + private final static float[] LETTER_M = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.6f,0f, + 0.2f,0.3f,0f, + 0.5f,0.6f,0f, + 0.5f,0.0f,0f, + }; + + private final static float[] LETTER_P = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.6f,0f, + 0.3f,0.6f,0f, + 0.4f,0.5f,0f, + 0.4f,0.3f,0f, + 0.3f,0.3f,0f, + 0.0f,0.3f,0f, + }; + private final static float[] LETTER_T = new float[]{ 0.2f,0.0f,0f, 0.2f,0.6f,0f, @@ -313,6 +443,12 @@ public final class Text extends Widget { 0.4f,0.6f,0f, }; + private final static float[] LETTER_V = new float[]{ + 0.0f,0.6f,0f, + 0.2f,0.0f,0f, + 0.4f,0.6f,0f + }; + private final static float[] LETTER_w = new float[]{ 0.0f,0.4f,0f, 0.0f,0.1f,0f, @@ -335,6 +471,12 @@ public final class Text extends Widget { 0.4f,0.0f,0f, }; + private final static float[] LETTER_v = new float[]{ + 0.0f,0.4f,0f, + 0.2f,0.0f,0f, + 0.4f,0.4f,0f, + }; + private final static float[] LETTER_r = new float[]{ 0.0f,0.4f,0f, 0.0f,0.0f,0f, @@ -355,10 +497,28 @@ public final class Text extends Widget { 0.4f,0.2f,0f, }; + private final static float[] LETTER_b = new float[]{ + 0.0f,0.0f,0f, + 0.0f,0.7f,0f, + 0.0f,0.3f,0f, + 0.1f,0.3f,0f, + 0.2f,0.4f,0f, + 0.3f,0.4f,0f, + 0.4f,0.3f,0f, + 0.4f,0.1f,0f, + 0.3f,0.0f,0f, + 0.2f,0.0f,0f, + 0.1f,0.1f,0f, + 0.0f,0.1f,0f + }; + final static Map LETTERS = new HashMap<>(); static { LETTERS.put('-',SYMBOL_MINUS); + LETTERS.put('.',SYMBOL_POINT); + LETTERS.put(',',SYMBOL_COMMA); + LETTERS.put(':',SYMBOL_COLON); LETTERS.put('0',_0); LETTERS.put('1',_1); @@ -372,6 +532,7 @@ public final class Text extends Widget { LETTERS.put('9',_9); LETTERS.put('a',LETTER_a); + LETTERS.put('b',LETTER_b); LETTERS.put('c',LETTER_c); LETTERS.put('d',LETTER_d); LETTERS.put('e',LETTER_e); @@ -379,101 +540,170 @@ public final class Text extends Widget { LETTERS.put('g',LETTER_g); LETTERS.put('h',LETTER_h); LETTERS.put('i',LETTER_i); + LETTERS.put('j',LETTER_j); + LETTERS.put('k',LETTER_k); LETTERS.put('m',LETTER_m); LETTERS.put('n',LETTER_n); LETTERS.put('l',LETTER_l); LETTERS.put('p',p); + LETTERS.put('q',LETTER_q); LETTERS.put('r',LETTER_r); LETTERS.put('s',s); LETTERS.put('o',LETTER_o); LETTERS.put('t',LETTER_t); LETTERS.put('u',LETTER_u); + LETTERS.put('v',LETTER_v); LETTERS.put('w',LETTER_w); LETTERS.put('x',LETTER_x); + LETTERS.put('y',LETTER_y); + LETTERS.put('z',LETTER_z); + LETTERS.put('A',LETTER_A); + LETTERS.put('B',LETTER_B); LETTERS.put('C',LETTER_C); LETTERS.put('L',LETTER_L); + LETTERS.put('M',LETTER_M); + LETTERS.put('P',LETTER_P); LETTERS.put('T',LETTER_T); + LETTERS.put('V',LETTER_V); } private final int rows; private final int columns; + private float padding; private String currentText = null; private Text(int columns, int rows) { + this(columns, rows, 0); + } + + private Text(int columns, int rows, float padding) { super(); this.columns = columns; this.rows = rows; + this.padding = padding; init(); } private void init(){ setVertexBuffer(IOUtils.createFloatBuffer(columns * rows * 12 * 3)); setColorsBuffer(IOUtils.createFloatBuffer(columns * rows * 12 * 4)); - setDimensions(new Dimensions(0,columns*0.5f,rows*0.7f,0,0,0)); + setDimensions(new Dimensions(0, + columns*(0.5f+padding * 2),rows*(0.7f+padding * 2),0,0,0)); Log.d("Text","Created text: "+ getDimensions()); } public static Text allocate(int columns, int rows){ - return new Text(columns, rows); + return allocate(columns, rows, 0); + } + + public static Text allocate(int columns, int rows, float padding){ + return new Text(columns, rows, padding); } public void update(String text){ if (text == null || text.equals(this.currentText)) return; + final String[] lines = text.split("\\r?\\n"); + final FloatBuffer vertexBuffer = getVertexBuffer(); - vertexBuffer.position(0); final FloatBuffer colorBuffer = getColorsBuffer(); - colorBuffer.position(0); int idx = 0; - for (int row=0; row=3 && i < data.length - 6 && + (data[i-3] == data[i] && data[i-2] == data[i+1] && data[i-1] == data[i+2] + || + (data[i+3] == data[i] && data[i+4] == data[i+1] && data[i+5] == data[i+2]))){ + vertexBuffer.put(idx++, data[i+2]-0.1f); + } else { + vertexBuffer.put(idx++, data[i+2]); + } } - vertexBuffer.put(data[data.length-3]+offsetX); - vertexBuffer.put(data[data.length-2]+offsetY); - vertexBuffer.put(data[data.length-1]); - - colorBuffer.put(0f); - colorBuffer.put(0f); - colorBuffer.put(0f); - colorBuffer.put(0f); + vertexBuffer.put(idx++, data[data.length-3]+offsetX); + vertexBuffer.put(idx++, data[data.length-2]+offsetY); + vertexBuffer.put(idx++, data[data.length-1]-0.1f); + + + + colorBuffer.put(idxColor++, 1f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + + boolean isBlind = false; for (int i=0; i=3 && i < data.length - 6 && + (data[i-3] == data[i] && data[i-2] == data[i+1] && data[i-1] == data[i+2])){ + // same vertex - blind spot + isBlind = true; + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + Log.v("Text","Blind spot: "+i); + } else { + colorBuffer.put(idxColor++,1f); + colorBuffer.put(idxColor++,1f); + colorBuffer.put(idxColor++,1f); + colorBuffer.put(idxColor++,1f); + } + } - colorBuffer.put(0f); - colorBuffer.put(0f); - colorBuffer.put(0f); - colorBuffer.put(0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + colorBuffer.put(idxColor++,0f); + + return idx; } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Widget.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Widget.java index e2c70d4d..69fc8740 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Widget.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/gui/Widget.java @@ -1,13 +1,18 @@ package org.andresoviedo.android_3d_model_engine.gui; import android.opengl.GLES20; +import android.opengl.Matrix; import android.os.SystemClock; import android.util.Log; import org.andresoviedo.android_3d_model_engine.animation.JointTransform; +import org.andresoviedo.android_3d_model_engine.drawer.Renderer; +import org.andresoviedo.android_3d_model_engine.drawer.RendererFactory; +import org.andresoviedo.android_3d_model_engine.model.Camera; import org.andresoviedo.android_3d_model_engine.model.Dimensions; import org.andresoviedo.android_3d_model_engine.model.Object3DData; import org.andresoviedo.util.event.EventListener; +import org.andresoviedo.util.io.IOUtils; import org.andresoviedo.util.math.Math3DUtils; import java.nio.FloatBuffer; @@ -15,7 +20,7 @@ import java.util.EventObject; import java.util.List; -public abstract class Widget extends Object3DData implements EventListener { +public class Widget extends Object3DData implements EventListener { public static class ClickEvent extends EventObject { @@ -97,13 +102,20 @@ public float getDy() { public static final int POSITION_TOP = 1; public static final int POSITION_MIDDLE = 4; public static final int POSITION_RIGHT = 5; + public static final int POSITION_LEFT = 3; public static final int POSITION_TOP_RIGHT = 2; public static final int POSITION_BOTTOM = 7; private static int counter = 0; - // we need this to calculate relative position - float ratio; + // every widget has it's own projection + protected final float[] cameraPosition = new float[]{0, 0, 10f}; + protected float[] projectionMatrix = new float[16]; + protected float[] viewMatrix = new float[16]; + + protected int width; + protected int height; + protected float ratio; final List widgets = new ArrayList<>(); @@ -120,6 +132,24 @@ public float getDy() { private Runnable animation; + protected Object3DData source; + + public static final float PADDING_01 = 0.1f; + public static final float PADDING_02 = 0.2f; + + private float padding; + + protected Widget(){ + + } + + public Widget(Object3DData source){ + this.source = source; + setVertexBuffer(source.getVertexBuffer()); + setDrawMode(source.getDrawMode()); + setColorsBuffer(source.getColorsBuffer()); + } + @Override public Object3DData setColor(float[] color) { super.setColor(color); @@ -222,10 +252,12 @@ public Object3DData setRelativeScale(float[] scale){ // That is, -1+1 is 100% parent dimension Dimensions parentDim = getParent().getCurrentDimensions(); float relScale = parentDim.getRelationTo(getCurrentDimensions()); - Log.d("Widget","Relative scale ("+getId()+"): "+relScale); + Log.v("Widget","Relative scale ("+getId()+"): "+relScale); float[] newScale = Math3DUtils.multiply(scale, relScale); - return super.setScale(newScale); + super.setScale(newScale); + return this; + } float[] getInitialScale() { @@ -246,21 +278,21 @@ public float[] getInitialPosition() { return initialPosition; } - static float[] calculatePosition_2(int relativePosition, Dimensions dimensions, float newScale, float ratio){ + float[] calculatePosition(int relativePosition, Dimensions dimensions, float newScale, float ratio){ float x, y, z = 0; switch (relativePosition) { case POSITION_TOP_LEFT: x = -ratio; - y = 1 - dimensions.getHeight() * newScale; + y = 1- dimensions.getHeight()/2f - dimensions.getCenter()[1]; break; case POSITION_TOP: - x = -(dimensions.getWidth() * newScale / 2); - y = 1 - dimensions.getHeight() * newScale; + x = -dimensions.getCenter()[0] / 2; + y = 1- dimensions.getHeight()/2f - dimensions.getCenter()[1]; break; case POSITION_TOP_RIGHT: - x = ratio - dimensions.getWidth() * newScale; - y = 1 - dimensions.getHeight() * newScale; + x = ratio - dimensions.getMax()[0]; + y = 1 - dimensions.getMax()[1]; break; case POSITION_MIDDLE: x = -(dimensions.getWidth() * newScale / 2); @@ -270,46 +302,13 @@ static float[] calculatePosition_2(int relativePosition, Dimensions dimensions, x = ratio -(dimensions.getWidth() * newScale); y = -(dimensions.getHeight() * newScale / 2); break; - case POSITION_BOTTOM: - x = -(dimensions.getWidth() * newScale / 2); - y = -1 + (dimensions.getHeight() * newScale / 2); - y -= (dimensions.getCenter()[1] * newScale); - break; - default: - throw new UnsupportedOperationException(); - } - return new float[]{x, y, z}; - } - - static float[] calculatePosition(int relativePosition, Dimensions dimensions, float newScale, float ratio){ - - float x, y, z = 0; - switch (relativePosition) { - case POSITION_TOP_LEFT: - x = -ratio; - y = 1 - dimensions.getHeight() * newScale; - Log.d("Widget","height:"+dimensions.getHeight()+", scale: "+newScale); - break; - case POSITION_TOP: - x = -(dimensions.getWidth() * newScale / 2); - y = 1 - dimensions.getHeight() * newScale; - break; - case POSITION_TOP_RIGHT: - x = ratio - dimensions.getWidth() * newScale; - y = 1 - dimensions.getHeight() * newScale; - break; - case POSITION_MIDDLE: - x = -(dimensions.getWidth() * newScale / 2); - y = -(dimensions.getHeight() * newScale / 2); - break; - case POSITION_RIGHT: - x = ratio -(dimensions.getWidth() * newScale); - y = -(dimensions.getHeight() * newScale / 2); + case POSITION_LEFT: + x = - ratio ; + y = -(dimensions.getHeight() / 2); break; case POSITION_BOTTOM: - x = -(dimensions.getWidth() * newScale / 2); - y = -1 + (dimensions.getHeight() * newScale / 2); - y -= (dimensions.getCenter()[1] * newScale); + x = -dimensions.getCenter()[0]; + y = - 1 + dimensions.getHeight() / 2 - dimensions.getCenter()[1] + padding ; break; default: throw new UnsupportedOperationException(); @@ -347,6 +346,22 @@ private static float[] unbox(Float[] boxed){ return ret; } + @Override + public void render(RendererFactory rendererFactory, Camera camera, float[] lightPosInWorldSpace, float[] colorMask) { + super.render(rendererFactory, camera, lightPosInWorldSpace, colorMask); + this.renderImpl(rendererFactory,lightPosInWorldSpace,colorMask); + for (int i = 0; i < widgets.size(); i++) { + widgets.get(i).render(rendererFactory, camera, lightPosInWorldSpace, colorMask); + } + } + + protected void renderImpl(RendererFactory rendererFactory, float[] lightPosInWorldSpace, float[] colorMask) { + if (!isVisible()) return; + onDrawFrame(); + Renderer drawer = rendererFactory.getDrawer(this, false, false, false, false, true); + drawer.draw(this, projectionMatrix, viewMatrix, -1, lightPosInWorldSpace, colorMask, cameraPosition); + } + public void onDrawFrame(){ if (animation != null) animation.run(); } @@ -378,10 +393,49 @@ public void setRatio(float ratio){ this.ratio = ratio; } + /** + * @param width horizontal screen pixels + * @param height vertical screen pixels + */ + public void setSize(int width, int height) { + this.width = width; + this.height = height; + this.ratio = (float) width / height; + + // setup view & projection + Matrix.setLookAtM(viewMatrix, 0, + cameraPosition[0], cameraPosition[1], cameraPosition[2], + 0, 0, 0, + 0, 1, 0); + Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, 1, 100); + + FloatBuffer vertexBuffer = IOUtils.createFloatBuffer(5 * 3); + vertexBuffer.put(-ratio).put(-1).put(-1); + vertexBuffer.put(+ratio).put(-1).put(-1); + vertexBuffer.put(+ratio).put(1).put(-1); + vertexBuffer.put(-ratio).put(1).put(-1); + vertexBuffer.put(-ratio).put(-1).put(-1); + setVertexBuffer(vertexBuffer); + + setVisible(true); + + } + public void addWidget(Widget widget) { this.widgets.add(widget); addListener(widget); + widget.viewMatrix = this.viewMatrix; + widget.projectionMatrix = this.projectionMatrix; + widget.setRatio(ratio); if (widget.getParent() == null) widget.setParent(this); Log.i("Widget","New widget: "+widget); } + + public float getPadding() { + return padding; + } + + public void setPadding(float padding) { + this.padding = padding; + } } \ No newline at end of file diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/AnimatedModel.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/AnimatedModel.java index 2d3d7407..5228b1b7 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/AnimatedModel.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/AnimatedModel.java @@ -147,53 +147,6 @@ public void updateAnimatedTransform(Joint joint) { getJointTransforms()[joint.getIndex()] = joint.getAnimatedTransform(); } - public Dimensions getCurrentDimensions() { - - // FIXME: dimensions when model is animated are different. what we can do ?? - if (true) return super.getCurrentDimensions(); - - if (this.currentDimensions == null) { - final float[] location = new float[4]; - final float[] ret = new float[4]; - - final Dimensions newDimensions = new Dimensions(); - - Log.i("AnimatedModel", "Calculating current dimensions..."); - Log.i("AnimatedModel", "id:" + getId() + ", elements:" + elements); - if (this.elements == null || this.elements.isEmpty()) { - for (int i = 0; i < vertexBuffer.capacity(); i += 3) { - location[0] = vertexBuffer.get(i); - location[1] = vertexBuffer.get(i + 1); - location[2] = vertexBuffer.get(i + 2); - location[3] = 1; - final float[] temp = new float[4]; - Matrix.multiplyMV(temp, 0, this.getBindShapeMatrix(), 0, location, 0); - Matrix.multiplyMV(ret, 0, this.getModelMatrix(), 0, temp, 0); - newDimensions.update(ret[0], ret[1], ret[2]); - } - } else { - for (Element element : getElements()) { - final IntBuffer indexBuffer = element.getIndexBuffer(); - for (int i = 0; i < indexBuffer.capacity(); i++) { - final int idx = indexBuffer.get(i); - location[0] = vertexBuffer.get(idx * 3); - location[1] = vertexBuffer.get(idx * 3 + 1); - location[2] = vertexBuffer.get(idx * 3 + 2); - location[3] = 1; - final float[] temp = new float[4]; - Matrix.multiplyMV(temp, 0, this.getBindShapeMatrix(), 0, location, 0); - Matrix.multiplyMV(ret, 0, this.getModelMatrix(), 0, temp, 0); - newDimensions.update(ret[0], ret[1], ret[2]); - } - } - } - this.currentDimensions = newDimensions; - - Log.d("AnimatedModel", "Calculated current dimensions for '" + getId() + "': " + this.currentDimensions); - } - return currentDimensions; - } - @Override public AnimatedModel clone() { final AnimatedModel ret = new AnimatedModel(); diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Animation.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Animation.java new file mode 100644 index 00000000..1499fdc4 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Animation.java @@ -0,0 +1,23 @@ +package org.andresoviedo.android_3d_model_engine.model; + +public class Animation { + + private final T target; + protected boolean finished; + + public Animation(T target) { + this.target = target; + } + + public void animate(){ + finished = true; + } + + public T getTarget() { + return target; + } + + public boolean isFinished() { + return finished; + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Camera.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Camera.java index 19a974b8..c6e07664 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Camera.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Camera.java @@ -9,34 +9,40 @@ public class Camera { - private static final float ROOM_CENTER_SIZE = 0.5f; - private static final float ROOM_SIZE = 1000; + private final BoundingBox centerBox = new BoundingBox("scene", -Constants.ROOM_CENTER_SIZE, Constants.ROOM_CENTER_SIZE, + -Constants.ROOM_CENTER_SIZE, Constants.ROOM_CENTER_SIZE, -Constants.ROOM_CENTER_SIZE, Constants.ROOM_CENTER_SIZE); + private final BoundingBox roomBox = new BoundingBox("scene", -Constants.ROOM_SIZE, Constants.ROOM_SIZE, + -Constants.ROOM_SIZE, Constants.ROOM_SIZE, -Constants.ROOM_SIZE, Constants.ROOM_SIZE); - private final BoundingBox centerBox = new BoundingBox("scene", -ROOM_CENTER_SIZE, ROOM_CENTER_SIZE, - -ROOM_CENTER_SIZE, ROOM_CENTER_SIZE, -ROOM_CENTER_SIZE, ROOM_CENTER_SIZE); - private final BoundingBox roomBox = new BoundingBox("scene", -ROOM_SIZE, ROOM_SIZE, - -ROOM_SIZE, ROOM_SIZE, -ROOM_SIZE, ROOM_SIZE); + private Camera delegate; - private float[] buffer = new float[12 + 12 + 16 + 16]; - private long animationCounter; - private Object[] lastAction; - private boolean changed = false; + private Animation animation; - // cache - private float[] coordinates = new float[16]; - // cache 2 - private float[] distanceToCenter = null; + private final float[] buffer = new float[12 + 12 + 16 + 16]; + private boolean changed = false; // new vector model - private float[] pos = new float[]{0,0,0,1}; - private float[] view = new float[]{0,0,0,1}; - private float[] up = new float[]{0,0,0,1}; + protected float[] pos = new float[]{0,0,0,1}; + protected float[] view = new float[]{0,0,0,1}; + protected float[] up = new float[]{0,0,0,1}; + + // transformation matrix + protected float[] matrix = new float[16]; + + // camera mode + private Projection projection = Projection.PERSPECTIVE; public Camera(float distance) { // Initialize variables... this(0, 0, distance, 0, 0, 0, 0, 1, 0); } + public Camera(Camera source) { + this.pos = source.pos; + this.view = source.view; + this.up = source.up; + } + public Camera(float xPos, float yPos, float zPos, float xView, float yView, float zView, float xUp, float yUp, float zUp) { // Here we set the camera to the values sent in to us. This is mostly @@ -53,58 +59,31 @@ public Camera(float xPos, float yPos, float zPos, float xView, float yView, floa this.up[2] = zUp; } - public synchronized void animate() { - if (lastAction == null || animationCounter == 0) { - lastAction = null; - animationCounter = 100; - return; - } - String method = (String) lastAction[0]; - if (method.equals("translate")) { - float dX = (Float) lastAction[1]; - float dY = (Float) lastAction[2]; - translateCameraImpl(dX * animationCounter / 100, dY * animationCounter / 100); - } else if (method.equals("rotate")) { - float rotZ = (Float) lastAction[1]; - RotateImpl(rotZ / 100 * animationCounter); - } - animationCounter--; + public Camera getDelegate() { + return delegate; } - public synchronized void MoveCameraZ(float direction) { - if (direction == 0) return; - MoveCameraZImpl(direction); - lastAction = new Object[]{"zoom", direction}; + public void setDelegate(Camera delegate) { + this.delegate = delegate; } - private void MoveCameraZImpl(float direction) { - // Moving the camera requires a little more then adding 1 to the z or - // subracting 1. - // First we need to get the direction at which we are looking. - float xLookDirection, yLookDirection, zLookDirection; - - // The look direction is the view minus the position (where we are). - xLookDirection = getxView() - pos[0]; - yLookDirection = getyView() - pos[1]; - zLookDirection = view[2] - pos[2]; - - // Normalize the direction. - float dp = Matrix.length(xLookDirection, yLookDirection, zLookDirection); - xLookDirection /= dp; - yLookDirection /= dp; - zLookDirection /= dp; - - float x = pos[0] + xLookDirection * direction; - float y = pos[1] + yLookDirection * direction; - float z = pos[2] + zLookDirection * direction; + public void setAnimation(Animation animation) { + this.animation = animation; + } + public Animation getAnimation() { + return this.animation; + } - if (isOutOfBounds(x, y, z)) return; + public void enable(){ + } - pos[0] = x; - pos[1] = y; - pos[2] = z; + public synchronized void animate() { + if (getAnimation() != null){ + getAnimation().animate(); + } + } - setChanged(true); + public synchronized void MoveCameraZ(float direction) { } /** @@ -115,7 +94,7 @@ private void MoveCameraZImpl(float direction) { * @param z z position * @return true if specified position is outside room "walls" or in the very center of the room */ - private boolean isOutOfBounds(float x, float y, float z) { + protected boolean isOutOfBounds(float x, float y, float z) { if (roomBox.outOfBound(x, y, z)) { Log.v("Camera", "Out of room walls. " + x + "," + y + "," + z); return true; @@ -143,164 +122,8 @@ private boolean isOutOfBounds(float x, float y, float z) { * @param dY the Y component of the user 2D vector, that is, a value between [-1,1] */ public synchronized void translateCamera(float dX, float dY) { - // Log.v("Camera","translate:"+dX+","+dY); - if (dX == 0 && dY == 0) return; - translateCameraImpl(dX, dY); - lastAction = new Object[]{"translate", dX, dY}; - } - - private void translateCameraImpl(float dX, float dY) { - float vlen; - - // Translating the camera requires a directional vector to rotate - // First we need to get the direction at which we are looking. - // The look direction is the view minus the position (where we are). - // Get the Direction of the view. - float xLook, yLook, zLook; - xLook = getxView() - pos[0]; - yLook = getyView() - pos[1]; - zLook = view[2] - pos[2]; - vlen = Matrix.length(xLook, yLook, zLook); - xLook /= vlen; - yLook /= vlen; - zLook /= vlen; - - // Arriba is the 3D vector that is **almost** equivalent to the 2D user Y vector - // Get the direction of the up vector - float xArriba, yArriba, zArriba; - xArriba = getxUp() - pos[0]; - yArriba = getyUp() - pos[1]; - zArriba = getzUp() - pos[2]; - // Normalize the Right. - vlen = Matrix.length(xArriba, yArriba, zArriba); - xArriba /= vlen; - yArriba /= vlen; - zArriba /= vlen; - - // Right is the 3D vector that is equivalent to the 2D user X vector - // In order to calculate the Right vector, we have to calculate the cross product of the - // previously calculated vectors... - - // The cross product is defined like: - // A x B = (a1, a2, a3) x (b1, b2, b3) = (a2 * b3 - b2 * a3 , - a1 * b3 + b1 * a3 , a1 * b2 - b1 * a2) - float xRight, yRight, zRight; - xRight = (yLook * zArriba) - (zLook * yArriba); - yRight = (zLook * xArriba) - (xLook * zArriba); - zRight = (xLook * yArriba) - (yLook * xArriba); - // Normalize the Right. - vlen = Matrix.length(xRight, yRight, zRight); - xRight /= vlen; - yRight /= vlen; - zRight /= vlen; - - // Once we have the Look & Right vector, we can recalculate where is the final Arriba vector, - // so its equivalent to the user 2D Y vector. - xArriba = (yRight * zLook) - (zRight * yLook); - yArriba = (zRight * xLook) - (xRight * zLook); - zArriba = (xRight * yLook) - (yRight * xLook); - // Normalize the Right. - vlen = Matrix.length(xArriba, yArriba, zArriba); - xArriba /= vlen; - yArriba /= vlen; - zArriba /= vlen; - - // coordinates = new float[] { pos[0], pos[1], pos[2], 1, xView, yView, zView, 1, xUp, yUp, zUp, 1 }; - coordinates[0] = pos[0]; - coordinates[1] = pos[1]; - coordinates[2] = pos[2]; - coordinates[3] = 1; - coordinates[4] = getxView(); - coordinates[5] = getyView(); - coordinates[6] = view[2]; - coordinates[7] = 1; - coordinates[8] = getxUp(); - coordinates[9] = getyUp(); - coordinates[10] = getzUp(); - coordinates[11] = 1; - - if (dX != 0 && dY != 0) { - - // in this case the user is drawing a diagonal line: \v ^\ v/ /^ - // so, we have to calculate the perpendicular vector of that diagonal - - // The perpendicular vector is calculated by inverting the X/Y values - // We multiply the initial Right and Arriba vectors by the User's 2D vector - xRight *= dY; - yRight *= dY; - zRight *= dY; - xArriba *= dX; - yArriba *= dX; - zArriba *= dX; - - // Then we add the 2 affected vectors to the the final rotation vector - float rotX, rotY, rotZ; - rotX = xRight + xArriba; - rotY = yRight + yArriba; - rotZ = zRight + zArriba; - vlen = Matrix.length(rotX, rotY, rotZ); - rotX /= vlen; - rotY /= vlen; - rotZ /= vlen; - - // in this case we use the vlen angle because the diagonal is not perpendicular - // to the initial Right and Arriba vectors - createRotationMatrixAroundVector(buffer, 24, vlen, rotX, rotY, rotZ); - } else if (dX != 0) { - // in this case the user is drawing an horizontal line: <-- ó --> - createRotationMatrixAroundVector(buffer, 24, dX, xArriba, yArriba, zArriba); - } else { - // in this case the user is drawing a vertical line: |^ v| - createRotationMatrixAroundVector(buffer, 24, dY, xRight, yRight, zRight); - } - multiplyMMV(buffer, 0, buffer, 24, coordinates, 0); - - if (isOutOfBounds(buffer[0], buffer[1], buffer[2])) return; - - pos[0] = buffer[0] / buffer[3]; - pos[1] = buffer[1] / buffer[3]; - pos[2] = buffer[2] / buffer[3]; - view[0] = buffer[4] / buffer[4 + 3]; - view[1] = buffer[4 + 1] / buffer[4 + 3]; - view[2] = buffer[4 + 2] / buffer[4 + 3]; - up[0] = buffer[8] / buffer[8 + 3]; - up[1] = buffer[8 + 1] / buffer[8 + 3]; - up[2] = buffer[8 + 2] / buffer[8 + 3]; - - setChanged(true); - - } - - private static void createRotationMatrixAroundVector(float[] matrix, int offset, float angle, float x, float y, - float z) { - float cos = (float) Math.cos(angle); - float sin = (float) Math.sin(angle); - float cos_1 = 1 - cos; - - // @formatter:off - matrix[offset] = cos_1 * x * x + cos; - matrix[offset + 1] = cos_1 * x * y - z * sin; - matrix[offset + 2] = cos_1 * z * x + y * sin; - matrix[offset + 3] = 0; - matrix[offset + 4] = cos_1 * x * y + z * sin; - matrix[offset + 5] = cos_1 * y * y + cos; - matrix[offset + 6] = cos_1 * y * z - x * sin; - matrix[offset + 7] = 0; - matrix[offset + 8] = cos_1 * z * x - y * sin; - matrix[offset + 9] = cos_1 * y * z + x * sin; - matrix[offset + 10] = cos_1 * z * z + cos; - matrix[offset + 11] = 0; - matrix[offset + 12] = 0; - matrix[offset + 13] = 0; - matrix[offset + 14] = 0; - matrix[offset + 15] = 1; - - // @formatter:on - } - - private static void multiplyMMV(float[] result, int retOffset, float[] matrix, int matOffet, float[] vector4Matrix, - int vecOffset) { - for (int i = 0; i < vector4Matrix.length / 4; i++) { - Matrix.multiplyMV(result, retOffset + (i * 4), matrix, matOffet, vector4Matrix, vecOffset + (i * 4)); + if (getDelegate() != null){ + getDelegate().translateCamera(dX, dY); } } @@ -310,6 +133,9 @@ public boolean hasChanged() { public void setChanged(boolean changed) { this.changed = changed; + if (getDelegate()!=null) { + getDelegate().setChanged(this.changed); + } } @Override @@ -318,52 +144,7 @@ public String toString() { + ", zView=" + view[2] + ", xUp=" + getxUp() + ", yUp=" + getyUp() + ", zUp=" + getzUp() + "]"; } - public synchronized void Rotate(float rotViewerZ) { - if (rotViewerZ == 0) return; - RotateImpl(rotViewerZ); - lastAction = new Object[]{"rotate", rotViewerZ}; - } - - private void RotateImpl(float rotViewerZ) { - if (Float.isNaN(rotViewerZ)) { - Log.w("Rot", "NaN"); - return; - } - float xLook = getxView() - pos[0]; - float yLook = getyView() - pos[1]; - float zLook = view[2] - pos[2]; - float vlen = Matrix.length(xLook, yLook, zLook); - xLook /= vlen; - yLook /= vlen; - zLook /= vlen; - - createRotationMatrixAroundVector(buffer, 24, rotViewerZ, xLook, yLook, zLook); - // float[] coordinates = new float[] { xPos, pos[1], pos[2], 1, xView, yView, zView, 1, xUp, yUp, zUp, 1 }; - coordinates[0] = pos[0]; - coordinates[1] = pos[1]; - coordinates[2] = pos[2]; - coordinates[3] = 1; - coordinates[4] = getxView(); - coordinates[5] = getyView(); - coordinates[6] = view[2]; - coordinates[7] = 1; - coordinates[8] = getxUp(); - coordinates[9] = getyUp(); - coordinates[10] = getzUp(); - coordinates[11] = 1; - multiplyMMV(buffer, 0, buffer, 24, coordinates, 0); - - pos[0] = buffer[0]; - pos[1] = buffer[1]; - pos[2] = buffer[2]; - view[0] = buffer[4]; - view[1] = buffer[4 + 1]; - view[2] = buffer[4 + 2]; - up[0] = buffer[8]; - up[1] = buffer[8 + 1]; - up[2] = buffer[8 + 2]; - - setChanged(true); + public synchronized void Rotate(float angle) { } public Camera[] toStereo(float eyeSeparation) { @@ -393,13 +174,13 @@ public Camera[] toStereo(float eyeSeparation) { float yViewRight = getyView() + crossRight[1] * eyeSeparation / 2; float zViewRight = view[2] + crossRight[2] * eyeSeparation / 2; - xViewLeft = getxView(); - yViewLeft = getyView(); - zViewLeft = view[2]; + //xViewLeft = getxView(); + //yViewLeft = getyView(); + //zViewLeft = view[2]; - xViewRight = getxView(); - yViewRight = getyView(); - zViewRight = view[2]; + //xViewRight = getxView(); + //yViewRight = getyView(); + //zViewRight = view[2]; Camera left = new Camera(xPosLeft, yPosLeft, zPosLeft, xViewLeft, yViewLeft, zViewLeft, getxUp(), getyUp(), getzUp()); @@ -408,38 +189,6 @@ public Camera[] toStereo(float eyeSeparation) { return new Camera[]{left, right}; } - public float[] getDistanceToCenterVector() { - if (distanceToCenter != null) return distanceToCenter; - - distanceToCenter = new float[4]; - - distanceToCenter[0] = -pos[0]; - distanceToCenter[1] = -pos[1]; - distanceToCenter[2] = -pos[2]; - distanceToCenter[3] = 1; - - return distanceToCenter; - } - - public void rotate(float degrees, float x, float y, float z) { - Matrix.setIdentityM(buffer, 24); // first matrix - Matrix.rotateM(buffer, 40, buffer, 24, degrees, x, y, z); // 2nd matrix - Matrix.multiplyMV(buffer, 0, buffer, 40, pos, 0); - pos[0] = buffer[0]; - pos[1] = buffer[1]; - pos[2] = buffer[2]; - Matrix.multiplyMV(buffer, 0, buffer, 40, view, 0); - view[0] = buffer[0]; - view[1] = buffer[1]; - view[2] = buffer[2]; - Matrix.multiplyMV(buffer, 0, buffer, 40, up, 0); - up[0] = buffer[0]; - up[1] = buffer[1]; - up[2] = buffer[2]; - - setChanged(true); - } - public float getxView() { return view[0]; } @@ -477,4 +226,126 @@ public float getzPos() { return pos[2]; } + public void set(float xPos, float yPos, float zPos, float xView, float yView, float zView, float xUp, float yUp, + float zUp) { + + // check that we have valid coordinates + + + // Here we set the camera to the values sent in to us. This is mostly + // used to set up a + // default position. + this.pos[0] = xPos; + this.pos[1] = yPos; + this.pos[2] = zPos; + this.view[0] = xView; + this.view[1] = yView; + this.view[2] = zView; + this.up[0] = xUp; + this.up[1] = yUp; + this.up[2] = zUp; + setChanged(true); + } + + public void set(float[] pos, float[] view, float[] up){ + this.set(pos[0], pos[1], pos[2],view[0], view[1], view[2], up[0],up[1], up[2]); + } + + public Projection getProjection() { + return projection; + } + + public void setProjection(Projection projection) { + this.projection = projection; + } + + public float getDistance() { + return Math3DUtils.length(this.pos); + } + + public float[] getPos() { + return this.pos; + } + + public float[] getRight() { + return Math3DUtils.normalize2(Math3DUtils.crossProduct(this.up, this.pos)); + } + + public float[] getUp() { + return this.up; + } + + public float[] getView() { + return this.view; + } + + // cellphone orientation + private int orientation; + /** + * Rotate using the current view vector + * + * @param angle angle in degrees + */ + public void setOrientation(int angle) { + + if (angle == this.orientation) return; + else if (Math.abs(this.orientation-angle)<5) return; + else{ + int previous = this.orientation; + this.orientation = angle; + angle = previous - angle; + } + + float dX = (float) Math.sin(Math.toRadians(angle)); + float dY = (float) Math.cos(Math.toRadians(angle)); + float[] right = Math3DUtils.to4d(getRight()); + + // screen orientation vector + /*float[] ovector1 = Math3DUtils.multiply(up, dX); + float[] ovector2 = Math3DUtils.multiply(getRight(), -dY); + float[] ovector = Math3DUtils.add(ovector1,ovector2); + Math3DUtils.normalize(ovector);*/ + + // rotation matrix + float[] matrix = new float[16]; + Matrix.setIdentityM(matrix,0); + Matrix.setRotateM(matrix,0, angle, getxPos(),getyPos(),getzPos()); + + float[] newUp = new float[4]; + Matrix.multiplyMV(newUp,0,matrix,0,up,0); + Math3DUtils.normalize(newUp); + + this.up[0] = newUp[0]; + this.up[1] = newUp[1]; + this.up[2] = newUp[2]; + + setChanged(true); + + /*float rota = -(float) Math.tan(angle-180)/5f; //-angle / 90f; + + float dot = Math.abs(Math3DUtils.dotProduct(up, newUp)); + if (Math.abs(dot) < 0.1f){ + Log.v("Camera","angle: "+angle+", rot:"+rota+", dx:"+dX+", dy:"+dY+" HIT! "+Math3DUtils.dotProduct(up,newUp)); + return; + } else { + Log.v("Camera","angle: "+angle+", rot:"+rota+", dx:"+dX+", dy:"+dY+" DOT! "+Math3DUtils.dotProduct(up,newUp)); + //return; + } + + Matrix.setIdentityM(matrix,0); + Matrix.setRotateM(matrix,0, -angle/90f, getxPos(),getyPos(),getzPos()); + Matrix.multiplyMV(newUp,0,matrix,0,up,0); + Math3DUtils.normalize(newUp); + + this.up[0] = newUp[0]; + this.up[1] = newUp[1]; + this.up[2] = newUp[2]; + setChanged(true);*/ + } + + public float[] getMatrix() { + Matrix.setLookAtM(this.matrix,0,getxPos(),getyPos(),getzPos(), + getxView(),getyView(),getzView(),getxUp(),getyUp(),getzUp()); + return matrix; + } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Constants.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Constants.java new file mode 100644 index 00000000..073b2344 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Constants.java @@ -0,0 +1,66 @@ +package org.andresoviedo.android_3d_model_engine.model; + +public class Constants { + /** + * Default unit factor for dimension on any axis + */ + public static final float UNIT = 1 * 100f; + /** + * Perspective camera. Near clipping panel + */ + public static final float near = UNIT / 100; + /** + * Perspective camera. Far clipping panel + */ + public static final float far = UNIT * UNIT * UNIT; + /** + * Default camera position on Z axis + */ + public static final float DEFAULT_CAMERA_POSITION = UNIT * 2; + + public static final float SKYBOX_SIZE = UNIT * 100; + + + static final float ROOM_CENTER_SIZE = 0.01f; + + static final float ROOM_SIZE = SKYBOX_SIZE * 100; + /** + * The nominal frames per second + */ + public static final int TOTAL_ANIMATION_FRAMES = 60; + /** + * If we need to approximate the vector to some discrete position, + * then we use this threshold to test if should + */ + public static final float SNAP_TO_GRID_THRESHOLD = 0.015f; + public static final float UNIT_SIN_1 = (float)Math.sqrt(1f/3f); // 0.58f + public static final float UNIT_SIN_2 = UNIT_SIN_1 + UNIT_SIN_1; + public static final float UNIT_SIN_3 = UNIT_SIN_2 * UNIT_SIN_1; + public static final float UNIT_SIN_5 = UNIT_SIN_3 * UNIT_SIN_2; + public static final float UNIT_0 = 0f; + public static final float UNIT_1 = 1f; + public static final float UNIT_2 = UNIT_1 + UNIT_1; + public static final float UNIT_3 = UNIT_2 + UNIT_1; + public static final float UNIT_5 = UNIT_3 + UNIT_2; + + // grid + public static final float GRID_WIDTH = 1f; + public static final float GRID_SIZE = 0.1f; + public static final float[] GRID_COLOR = {0.25f, 0.25f, 0.25f, 0.5f}; + + public static final float[] COLOR_RED = {1,0,0,1}; + public static final float[] COLOR_RED_TRANSLUCENT = {1,0,0,0.25f}; + public static final float[] COLOR_GREEN = {0,1,0,1}; + public static final float[] COLOR_GREEN_TRANSLUCENT = {0,1,0,0.25f}; + public static final float[] COLOR_BLUE = {0,0,1,1f}; + public static final float[] COLOR_BLUE_TRANSLUCENT = {0,0,1,0.25f}; + + public static final float[] COLOR_WHITE = {1, 1, 1, 1}; + public static final float[] COLOR_BLACK = {0, 0, 0, 1}; + public static final float[] COLOR_GRAY = {0.5f, 0.5f, 0.5f, 1f}; + public static final float[] COLOR_HALF_TRANSPARENT = {1f, 1f, 1f, 0.5f}; + private static final float[] COLOR_ALMOST_TRANSPARENT = {1f, 1f, 1f, 0.1f}; + public static final float[] COLOR_BIT_TRANSPARENT = {1f, 1f, 1f, 0.9f}; + // stereoscopic variables + public static float EYE_DISTANCE = 6.4f; +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Dimensions.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Dimensions.java index 63a993a5..47e160e5 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Dimensions.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Dimensions.java @@ -1,6 +1,6 @@ package org.andresoviedo.android_3d_model_engine.model; -import org.andresoviedo.util.math.Math3DUtils; +import android.opengl.Matrix; import java.text.DecimalFormat; import java.util.Arrays; @@ -13,9 +13,9 @@ public class Dimensions { private float farPt = Float.MAX_VALUE, nearPt = -Float.MAX_VALUE; // on z-axis // min max center - private final float[] center = new float[]{0,0,0}; - private final float[] min = new float[]{0,0,0}; - private final float[] max = new float[]{0,0,0}; + private final float[] center = new float[]{0,0,0,1}; + private final float[] min = new float[]{0,0,0,1}; + private final float[] max = new float[]{0,0,0,1}; // whether at least 1 vertex was processed private boolean initialized = false; @@ -27,6 +27,26 @@ public Dimensions() { //(); } + public Dimensions(Dimensions original, float[] matrix){ + float[] newMin = new float[4]; + float[] newMax = new float[4]; + Matrix.multiplyMV(newMin,0,matrix,0,original.getMin(),0); + Matrix.multiplyMV(newMax,0,matrix,0,original.getMax(),0); + float[][] points = new float[8][4]; + points[0] = new float[]{newMin[0], newMin[1], newMin[2], newMin[3]}; + points[1] = new float[]{newMax[0], newMin[1], newMin[2], newMin[3]}; + points[2] = new float[]{newMin[0], newMax[1], newMin[2], newMin[3]}; + points[3] = new float[]{newMin[0], newMin[1], newMax[2], newMin[3]}; + + points[4] = new float[]{newMax[0], newMax[1], newMax[2], newMax[3]}; + points[5] = new float[]{newMin[0], newMax[1], newMax[2], newMax[3]}; + points[6] = new float[]{newMax[0], newMin[1], newMax[2], newMax[3]}; + points[7] = new float[]{newMax[0], newMax[1], newMin[2], newMax[3]}; + for (int i=0; i} + * This is the bind shape transform found in skin (ie. {@code } */ public void setBindShapeMatrix(float[] matrix) { if (matrix == null) return; @@ -576,7 +598,7 @@ public void setBindShapeMatrix(float[] matrix) { this.vertexBuffer.put(i + 1, shaped[1]); this.vertexBuffer.put(i + 2, shaped[2]); } - updateModelDimensions(); + updateDimensions(); } public float[] getBindTransform() { @@ -585,19 +607,66 @@ public float[] getBindTransform() { private void updateModelMatrix() { + if (isReadOnly()) return; + Matrix.setIdentityM(modelMatrix, 0); - if (rotation1 != null) { - //Matrix.rotateM(modelMatrix, 0, rotation1[0], 1f, 0f, 0f); - Matrix.rotateM(modelMatrix, 0, rotation1[1], 0, 1f, 0f); - //Matrix.rotateM(modelMatrix, 0, rotation1[2], 0, 0, 1f); + //float[] axisAngle = orientation.toAxisAngle(); + //Matrix.rotateM(modelMatrix, 0, axisAngle[3], axisAngle[0], axisAngle[1], axisAngle[2]); + //Log.v("Object3DData","angle:"+Arrays.toString(axisAngle)) + + if (getRotation() != null) { + Matrix.rotateM(modelMatrix, 0, getRotation()[2], 0, 0, 1f); + Matrix.rotateM(modelMatrix, 0, getRotation()[1], 0, 1f, 0f); + Matrix.rotateM(modelMatrix, 0, getRotation()[0], 1f, 0f, 0f); } if (getLocation() != null) { Matrix.translateM(modelMatrix, 0, getLocationX(), getLocationY(), getLocationZ()); } - if (rotation2 != null && rotation2Location != null) { + if (getScale() != null) { + Matrix.scaleM(modelMatrix, 0, getScaleX(), getScaleY(), getScaleZ()); + } + + float[] orientationFix = new float[16]; + orientation.toRotationMatrix(orientationMatrix); + Matrix.multiplyMM(orientationFix,0,modelMatrix,0,orientationMatrix,0); + System.arraycopy(orientationFix,0,modelMatrix,0,orientationFix.length); + + if (isCentered()) { + Matrix.translateM(modelMatrix, 0, -getDimensions().getCenter()[0], + -getDimensions().getCenter()[1], -getDimensions().getCenter()[2]); + } + + /* + + if (getScale() != null) { + Matrix.scaleM(modelMatrix, 0, getScaleX(), getScaleY(), getScaleZ()); + } + + float[] temp = new float[16]; + orientation.toRotationMatrix(temp); + Matrix.multiplyMM(temp,0,orientationMatrix,0,modelMatrix,0); + System.arraycopy(temp,0,modelMatrix,0, temp.length);*/ + + /*Matrix.translateM(modelMatrix, 0, -getDimensions().getCenter()[0], + -getDimensions().getCenter()[1], -getDimensions().getCenter()[2]);*/ + + // apply rotation (euler) + /*if (rotation1 != null) { + //Matrix.rotateM(modelMatrix, 0, rotation1[0], 1f, 0f, 0f); + Matrix.rotateM(modelMatrix, 0, rotation1[1], 0, 1f, 0f); + //Matrix.rotateM(modelMatrix, 0, rotation1[2], 0, 0, 1f); + }*/ + + /*Matrix.translateM(modelMatrix, 0, getDimensions().getCenter()[0], + getDimensions().getCenter()[1], getDimensions().getCenter()[2]);*/ + + + + + /*if (rotation2 != null && rotation2Location != null) { Matrix.translateM(modelMatrix, 0, rotation2Location[0], rotation2Location[1], rotation2Location[2]); Matrix.rotateM(modelMatrix, 0, getRotation2()[0], 1f, 0f, 0f); Matrix.rotateM(modelMatrix, 0, getRotation2()[1], 0, 1f, 0f); @@ -608,11 +677,12 @@ private void updateModelMatrix() { Matrix.rotateM(modelMatrix, 0, getRotation()[0], 1f, 0f, 0f); Matrix.rotateM(modelMatrix, 0, getRotation()[1], 0, 1f, 0f); Matrix.rotateM(modelMatrix, 0, getRotationZ(), 0, 0, 1f); - } + }*/ + + // apply orientation (quaternion) + + - if (getScale() != null) { - Matrix.scaleM(modelMatrix, 0, getScaleX(), getScaleY(), getScaleZ()); - } if (this.bindTransform == null) { // geometries not linked to any joint does not have bind transform @@ -665,7 +735,7 @@ public FloatBuffer getVertexBuffer() { public Object3DData setVertexBuffer(FloatBuffer vertexBuffer) { this.vertexBuffer = vertexBuffer; - updateModelDimensions(); + updateDimensions(); return this; } @@ -707,55 +777,19 @@ public Object3DData setColorsBuffer(FloatBuffer colorsBuffer) { return this; } - protected void updateModelDimensions() { - // FIXME: this breaks GUI - //this.currentDimensions = null; - - /*final float[] location = new float[4]; - final float[] ret = new float[4]; - - final Dimensions dimensions = new Dimensions(); - final Dimensions currentDimensions = new Dimensions(); - - if (this.elements == null || this.elements.isEmpty()){ - for (int i = 0; i < vertexBuffer.capacity(); i += 3) { - if (getBindTransform() != null) { - location[0] = vertexBuffer.get(i); - location[1] = vertexBuffer.get(i + 1); - location[2] = vertexBuffer.get(i + 2); - location[3] = 1; - Matrix.multiplyMV(ret, 0, this.getModelMatrix(), 0, location, 0); - currentDimensions.update(ret[0], ret[1], ret[2]); - } else { - currentDimensions.update(vertexBuffer.get(i), vertexBuffer.get(i + 1), vertexBuffer.get(i + 2)); - } - dimensions.update(vertexBuffer.get(i), vertexBuffer.get(i + 1), vertexBuffer.get(i + 2)); - } - } - else { - for (Element element : getElements()) { - final IntBuffer indexBuffer = element.getIndexBuffer(); - for (int i = 0; i < indexBuffer.capacity(); i++) { - final int idx = indexBuffer.get(i); - location[0] = vertexBuffer.get(idx * 3); - location[1] = vertexBuffer.get(idx * 3 + 1); - location[2] = vertexBuffer.get(idx * 3 + 2); - location[3] = 1; - Matrix.multiplyMV(ret, 0, this.getModelMatrix(), 0, location, 0); - currentDimensions.update(ret[0], ret[1], ret[2]); - - dimensions.update(location[0], location[1], location[2]); - } - } - } - this.dimensions = dimensions; - this.currentDimensions = currentDimensions; - - Log.d("Object3DData","New dimensions for '"+getId()+"': "+this.dimensions+", real: "+this.currentDimensions);*/ + protected void updateDimensions() { + this.dimensions = null; } public BoundingBox getBoundingBox() { - return BoundingBox.create(getId() + "_BoundingBox1", getDimensions(), getModelMatrix()); + return BoundingBox.create("bbox_" + getId(), getDimensions(), Math3DUtils.IDENTITY_MATRIX); + } + + /** + * @return the current bounding box + */ + public BoundingBox getCurrentBoundingBox() { + return BoundingBox.create("bbox_" + getId(), getDimensions(), getModelMatrix()); } public void addError(String error) { @@ -768,7 +802,7 @@ public List getErrors() { public Object3DData setElements(List elements) { this.elements = elements; - this.updateModelDimensions(); + this.updateDimensions(); return this; } @@ -788,6 +822,17 @@ public float[] getRotation2Location() { return rotation2Location; } + public Quaternion getOrientation() { + return orientation; + } + + public Object3DData setOrientation(Quaternion orientation) { + this.orientation = orientation; + updateModelMatrix(); + setChanged(true); + return this; + } + @Override public Object3DData clone() { Object3DData ret = new Object3DData(); @@ -800,7 +845,7 @@ void copy(Object3DData ret) { ret.setLocation(this.getLocation().clone()); ret.setScale(this.getScale().clone()); ret.setRotation(this.getRotation().clone()); - ret.setCurrentDimensions(this.getCurrentDimensions()); + //ret.setCurrentDimensions(this.getCurrentDimensions()); ret.setVertexBuffer(this.getVertexBuffer()); ret.setNormalsBuffer(this.getNormalsBuffer()); ret.setColorsBuffer(this.getColorsBuffer()); @@ -824,7 +869,7 @@ public String toString() { ", vertices: " + (vertexBuffer != null ? vertexBuffer.capacity() / 3 : 0) + ", normals: " + (normalsBuffer != null ? normalsBuffer.capacity() / 3 : 0) + ", dimensions: " + this.dimensions + - ", current dimensions: " + this.currentDimensions + + //", current dimensions: " + this.currentDimensions + ", material=" + getMaterial() + ", elements=" + getElements() + '}'; diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Projection.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Projection.java new file mode 100644 index 00000000..ee478d6f --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/model/Projection.java @@ -0,0 +1,6 @@ +package org.andresoviedo.android_3d_model_engine.model; + +public enum Projection { + + PERSPECTIVE, ISOMETRIC, ORTHOGRAPHIC, FREE +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Axis.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Axis.java index fe76145a..463db39c 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Axis.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Axis.java @@ -17,7 +17,7 @@ public final class Axis { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, // z- 0.95f, 0.05f, 0, 1, 0, 0, 0.95f, -0.05f, 0, 1, 0f, 0f, // Arrow X (>) - -0.95f, 0.05f, 0, -1, 0, 0, -0.95f, -0.05f, 0, -1, 0f, 0f, // Arrow X (<) + // -0.95f, 0.05f, 0, -1, 0, 0, -0.95f, -0.05f, 0, -1, 0f, 0f, // Arrow X (<) -0.05f, 0.95f, 0, 0, 1, 0, 0.05f, 0.95f, 0, 0, 1f, 0f, // Arrox Y (^) -0.05f, 0, 0.95f, 0, 0, 1, 0.05f, 0, 0.95f, 0, 0, 1, // Arrox z (v) @@ -28,8 +28,31 @@ public final class Axis { //@formatter:on }; + private final static float[] axisColorLinesData = new float[]{ + //@formatter:off + 1, 0, 0, 1, 1, 0, 0, 1, // right + 1, 0, 0, 1, 1, 0, 0, 1, // left + 0, 1, 0, 1, 0, 1, 0, 1, // up + 0, 1, 0, 1, 0, 1, 0, 1, // down + 0, 0, 1, 1, 0, 0, 1, 1, // z+ + 0, 0, 1, 1, 0, 0, 1, 1, // z- + + 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, // Arrow X (>) + // 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, // Arrow X (<) + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // Arrow X (^) + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, // Arrow X (v) + + + 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, // Letter X + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // Letter Y + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 1, 0, 0, 1, 1, // letter z + //@formatter:on + }; + public static Object3DData build() { return new Object3DData(IOUtils.createFloatBuffer(axisVertexLinesData.length).put(axisVertexLinesData)) - .setDrawMode(GLES20.GL_LINES); + .setDrawMode(GLES20.GL_LINES). + setColorsBuffer(IOUtils.createFloatBuffer(axisColorLinesData.length).put(axisColorLinesData)); } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/BoundingBox.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/BoundingBox.java index 14bc8233..e0951284 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/BoundingBox.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/BoundingBox.java @@ -7,8 +7,6 @@ import org.andresoviedo.android_3d_model_engine.model.Object3DData; import org.andresoviedo.util.io.IOUtils; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; @@ -81,11 +79,7 @@ public static Object3DData build(Object3DData obj) { return new Object3DData(vertices, indexBuffer).setDrawModeList(drawList) .setDrawMode(GLES20.GL_LINE_LOOP) - .setLocation(obj.getLocation()) - .setScale(obj.getScale()) - .setRotation(obj.getRotation()) .setDrawUsingArrays(false) - .setBindTransform(obj.getBindTransform()) .setId(obj.getId() + "_boundingBox"); } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Skeleton.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Skeleton.java index 02da7685..eb18aa21 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Skeleton.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/Skeleton.java @@ -32,6 +32,8 @@ public static AnimatedModel build(AnimatedModel animatedModel) { skeleton.setId(animatedModel.getId() + "-skeleton"); skeleton.setDrawMode(GLES20.GL_TRIANGLES); skeleton.setDrawUsingArrays(true); + skeleton.setModelMatrix(animatedModel.getModelMatrix()); + skeleton.setReadOnly(true); // log event Log.i("Skeleton", "Building skeleton... joints: " + skeleton.getJointCount()); diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/SkyBox.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/SkyBox.java index eace3c10..419574c9 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/SkyBox.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/objects/SkyBox.java @@ -64,13 +64,6 @@ public class SkyBox { 1.0f, -1.0f, 1.0f }; - static { - - for (int i=0; i 82,842712475 total max width to fit viewport + * */ - private static final float DEFAULT_MAX_MODEL_SIZE = 100; - /** - * Camera position on Z axis - */ - private static final float DEFAULT_CAMERA_POSITION = DEFAULT_MAX_MODEL_SIZE / 2 + 25; /** * Parent component */ @@ -61,10 +68,6 @@ public class SceneLoader implements LoadListener, EventListener { * 0 = obj, 1 = stl, 2 = dae */ private final int type; - /** - * OpenGL view - */ - private GLSurfaceView glView; /** * List of 3D models */ @@ -76,7 +79,7 @@ public class SceneLoader implements LoadListener, EventListener { /** * Point of view camera */ - private Camera camera = new Camera(DEFAULT_CAMERA_POSITION); + private Camera camera = new Camera(Constants.DEFAULT_CAMERA_POSITION); /** * Blender uses different coordinate system. * This is a patch to turn camera and SkyBox 90 degree on X axis @@ -186,13 +189,23 @@ public class SceneLoader implements LoadListener, EventListener { private Map originalDimensions = new HashMap<>(); private Map originalTransforms = new HashMap<>(); - public SceneLoader(Activity main, URI uri, int type, GLSurfaceView glView) { + private List listeners = new ArrayList<>(); + + public SceneLoader(Activity main) { + this(main, null, -1); + } + + public SceneLoader(Activity main, URI uri, int type) { this.parent = main; this.uri = uri; this.type = type; - this.glView = glView; - lightBulb.setLocation(new float[]{0, 0, DEFAULT_CAMERA_POSITION}); + float light_distance = Constants.SKYBOX_SIZE; + lightBulb.setLocation(new float[]{light_distance,light_distance/2,0}); + } + + public void addListener(EventListener listener){ + this.listeners.add(listener); } public void init() { @@ -220,10 +233,12 @@ public void fixCoordinateSystem() { for (int i = 0; i < objects.size(); i++) { final Object3DData objData = objects.get(i); if (objData.getAuthoringTool() != null && objData.getAuthoringTool().toLowerCase().contains("blender")) { - getCamera().rotate(90f, 1, 0, 0); + Quaternion quaternion = Quaternion.getQuaternion(new float[]{1, 0, 0}, (float) (Math.PI/2f)); + quaternion.normalize(); + objData.setOrientation(quaternion); Log.i("SceneLoader", "Fixed coordinate system to 90 degrees on x axis. object: " + objData.getId()); this.isFixCoordinateSystem = true; - break; + //break; } } } @@ -251,13 +266,8 @@ public final void onDrawFrame() { animateLight(); - // smooth camera transition - camera.animate(); - // initial camera animation. animate if user didn't touch the screen - if (!userHasInteracted) { - animateCamera(); - } + animateCamera(); if (objects.isEmpty()) return; @@ -275,11 +285,16 @@ private void animateLight() { // animate light - Do a complete rotation every 5 seconds. long time = SystemClock.uptimeMillis() % 5000L; float angleInDegrees = (360.0f / 5000.0f) * ((int) time); - lightBulb.setRotation1(new float[]{0, angleInDegrees, 0}); + lightBulb.setRotation(new float[]{0, angleInDegrees, 0}); } private void animateCamera() { - camera.translateCamera(0.0005f, 0f); + // smooth camera transition + camera.animate(); + + if (!userHasInteracted) { + camera.translateCamera(0.0005f, 0f); + } } public final synchronized void addObject(Object3DData obj) { @@ -303,12 +318,6 @@ public final synchronized void addGUIObject(Object3DData obj) { // requestRender(); } - private void requestRender() { - if (glView != null) { - glView.requestRender(); - } - } - public final synchronized List getObjects() { return objects; } @@ -350,7 +359,7 @@ public final void toggleWireframe() { makeToastText("Normals", Toast.LENGTH_SHORT); break; } - requestRender(); + //requestRender(); } public final boolean isDrawWireframe() { @@ -363,7 +372,7 @@ public final boolean isDrawPoints() { public final void toggleBoundingBox() { this.drawBoundingBox = !drawBoundingBox; - requestRender(); + //requestRender(); } public final boolean isDrawBoundingBox() { @@ -402,7 +411,7 @@ public final void toggleLighting() { this.rotatingLight = true; makeToastText("Light on", Toast.LENGTH_SHORT); } - requestRender(); + //requestRender(); } public final void toggleAnimation() { @@ -420,12 +429,13 @@ public final void toggleAnimation() { public final void toggleSmooth() { for (int i = 0; i < getObjects().size(); i++) { + if (getObjects().get(i).getMeshData() == null) continue; if (!this.isSmooth) { - getObjects().get(0).getMeshData().smooth(); + getObjects().get(i).getMeshData().smooth(); } else { - getObjects().get(0).getMeshData().unSmooth(); + getObjects().get(i).getMeshData().unSmooth(); } - getObjects().get(0).getMeshData().refreshNormalsBuffer(); + getObjects().get(i).getMeshData().refreshNormalsBuffer(); } this.isSmooth = !this.isSmooth; } @@ -617,7 +627,6 @@ public synchronized void onLoad(Object3DData data) { // rescale objects so they fit in the viewport //rescale(this.getObjects(), DEFAULT_MAX_MODEL_SIZE, new float[3]); - } @Override @@ -643,13 +652,19 @@ public synchronized void onLoadComplete() { ContentUtils.setThreadActivity(null); // rescale all object so they fit in the screen - rescale(this.getObjects(), DEFAULT_MAX_MODEL_SIZE, new float[3]); + //rescale(this.getObjects(), DEFAULT_MAX_MODEL_SIZE, new float[3]); + if (this.getObjects().size() == 1){ + this.getObjects().get(0).setCentered(true); + } // fix coordinate system fixCoordinateSystem(); + + // rescale objects so they all fit in the viewport + rescale(this.getObjects(), Constants.UNIT, new float[3]); } - private void rescale(List objs) { + private void rescale(List objs, float size) { Log.v("SceneLoader", "Rescaling objects... " + objs.size()); // get largest object in scene @@ -664,7 +679,7 @@ private void rescale(List objs) { Log.v("SceneLoader", "Object largest dimension: " + largest); // rescale objects - float ratio = DEFAULT_MAX_MODEL_SIZE / largest; + float ratio = size / largest; Log.v("SceneLoader", "Scaling " + objs.size() + " objects with factor: " + ratio); float[] newScale = new float[]{ratio, ratio, ratio}; for (Object3DData data : objs) { @@ -686,6 +701,7 @@ public Object3DData getSelectedObject() { private void setSelectedObject(Object3DData selectedObject) { this.selectedObject = selectedObject; + AndroidUtils.fireEvent(listeners, new SelectedObjectEvent(this, selectedObject)); } public void loadTexture(Object3DData obj, Uri uri) throws IOException { @@ -705,31 +721,70 @@ public final boolean isRotatingLight() { return rotatingLight; } - public void setView(ModelSurfaceView view) { - this.glView = view; - } - @Override public boolean onEvent(EventObject event) { - //Log.v("SceneLoader","Processing event... "+event); + Object3DData selectedObject = getSelectedObject(); if (event instanceof TouchEvent) { userHasInteracted = true; + float[] right = camera.getRight(); + float[] up = camera.getUp(); + float[] pos = camera.getPos().clone(); + Math3DUtils.normalize(pos); + TouchEvent touch = (TouchEvent) event; + if (touch.getAction() == TouchEvent.Action.ROTATE && selectedObject != null) { + + float angle = touch.getAngle(); + float factor = 1f; //1/360f * touch.getLength(); + + + // Log.v("SceneLoader", "Q: Quaternion angle: " + Math.toDegrees(angle) + " ,dx:" + touch.getdX() + ", dy:" + -touch.getdY()); + Quaternion q0 = Quaternion.getQuaternion(pos, angle * factor); + //q0.normalize(); + + Quaternion multiply = Quaternion.multiply(selectedObject.getOrientation(),q0); + selectedObject.setOrientation(multiply); + } + else if (touch.getAction() == TouchEvent.Action.MOVE && selectedObject != null) { + + float angle = (float) (Math.atan2(-touch.getdY(), touch.getdX())); + Log.v("SceneLoader", "Rotating (axis:var): " + Math.toDegrees(angle) + " ,dx:" + touch.getdX() + ", dy:" + -touch.getdY()); + + float[] rightd = Math3DUtils.multiply(right, touch.getdY()); + float[] upd = Math3DUtils.multiply(up, touch.getdX()); + float[] rot = Math3DUtils.add(rightd,upd); + if (Math3DUtils.length(rot)>0) { + Math3DUtils.normalize(rot); + } else { + rot = new float[]{1,0,0}; + } + + float angle1 = -touch.getLength() / 360; + Quaternion q1 = Quaternion.getQuaternion(rot, angle1); + //q1.normalize(); + + Quaternion multiply = Quaternion.multiply(selectedObject.getOrientation(), q1); + //multiply.normalize(); + + selectedObject.setOrientation(multiply); + + } } else if (event instanceof CollisionEvent) { + this.userHasInteracted = true; Object3DData objectToSelect = ((CollisionEvent) event).getObject(); Object3DData point = ((CollisionEvent) event).getPoint(); if (isCollision() && point != null) { + Log.v("SceneLoader", "Adding collision point " + point); addObject(point); - } else { - if (getSelectedObject() == objectToSelect) { - Log.i("SceneLoader", "Unselected object " + objectToSelect.getId()); - Log.d("SceneLoader", "Unselected object " + objectToSelect); + } + if (selectedObject == objectToSelect) { + Log.v("SceneLoader", "Unselected object " + objectToSelect); setSelectedObject(null); } else { Log.i("SceneLoader", "Selected object " + objectToSelect.getId()); Log.d("SceneLoader", "Selected object " + objectToSelect); setSelectedObject(objectToSelect); } - } + } return true; } @@ -830,6 +885,10 @@ private void rescale(List datas, float newScale, float[] newPositi final float[] finalScale = new float[]{scaleFactor, scaleFactor, scaleFactor}; Log.d("SceneLoader", "New scale: " + scaleFactor); + if (scaleFactor > 0.5f && scaleFactor < 1.5f){ + return; + } + // calculate the global center float centerX = (maxRight + maxLeft) / 2; float centerY = (maxTop + maxBottom) / 2; @@ -870,7 +929,7 @@ private void rescale(List datas, float newScale, float[] newPositi Log.v("SceneLoader", "Mew model location: " + Arrays.toString(data.getLocation())); // center - data.translate(globalDifference); + //data.translate(globalDifference); Log.v("SceneLoader", "Mew model translated: " + Arrays.toString(data.getLocation())); } @@ -902,4 +961,19 @@ private void rescale(List datas, float newScale, float[] newPositi } + public void onOrientationChanged(int orientation) { + /*if (orientation == 0 || orientation == 180) { + Log.v("ModelActivity","onOrientationChanged: portrait: "+orientation); + } else if (orientation == 90 || orientation == 270) { + Log.v("ModelActivity","onOrientationChanged: landscape: "+orientation); + } else { + Log.v("ModelActivity","onOrientationChanged: orientation: "+orientation); + }*/ + Log.v("SceneLoader","onOrientationChanged: orientation: "+orientation); + camera.setOrientation(orientation); + } + + public void setCamera(Camera camera) { + this.camera = camera; + } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/util/Rescaler.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/util/Rescaler.java index 4862749d..c08c775e 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/util/Rescaler.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/util/Rescaler.java @@ -10,9 +10,9 @@ public class Rescaler { public static void rescale(Object3DData object3DData, float maxSize) { - float leftPt = Float.MAX_VALUE, rightPt = Float.MIN_VALUE; // on x-axis - float topPt = Float.MIN_VALUE, bottomPt = Float.MAX_VALUE; // on y-axis - float farPt = Float.MAX_VALUE, nearPt = Float.MIN_VALUE; // on z-axis + float leftPt = Float.MAX_VALUE, rightPt = -Float.MAX_VALUE; // on x-axis + float topPt = -Float.MAX_VALUE, bottomPt = Float.MAX_VALUE; // on y-axis + float farPt = Float.MAX_VALUE, nearPt = -Float.MAX_VALUE; // on z-axis FloatBuffer vertexBuffer = object3DData.getVertexBuffer(); if (vertexBuffer == null) { @@ -20,7 +20,7 @@ public static void rescale(Object3DData object3DData, float maxSize) { return; } - Log.i("Rescaler", "Calculating dimensions for '" + object3DData.getId() + "..."); + Log.i("Rescaler", "Rescaling '" + object3DData.getId() + "..."); for (int i = 0; i < vertexBuffer.capacity(); i += 3) { if (vertexBuffer.get(i) > rightPt) rightPt = vertexBuffer.get(i); diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/FPSEvent.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/FPSEvent.java new file mode 100644 index 00000000..d9f3a50a --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/FPSEvent.java @@ -0,0 +1,17 @@ +package org.andresoviedo.android_3d_model_engine.view; + +import java.util.EventObject; + +public class FPSEvent extends EventObject { + + private final int fps; + + public FPSEvent(Object source, int fps) { + super(source); + this.fps = fps; + } + + public int getFps() { + return fps; + } +} diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelRenderer.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelRenderer.java index a714e338..23cd6706 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelRenderer.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelRenderer.java @@ -12,8 +12,10 @@ import org.andresoviedo.android_3d_model_engine.drawer.RendererFactory; import org.andresoviedo.android_3d_model_engine.model.AnimatedModel; import org.andresoviedo.android_3d_model_engine.model.Camera; +import org.andresoviedo.android_3d_model_engine.model.Constants; import org.andresoviedo.android_3d_model_engine.model.Element; import org.andresoviedo.android_3d_model_engine.model.Object3DData; +import org.andresoviedo.android_3d_model_engine.model.Projection; import org.andresoviedo.android_3d_model_engine.objects.Axis; import org.andresoviedo.android_3d_model_engine.objects.BoundingBox; import org.andresoviedo.android_3d_model_engine.objects.Grid; @@ -23,15 +25,17 @@ import org.andresoviedo.android_3d_model_engine.objects.SkyBox; import org.andresoviedo.android_3d_model_engine.objects.Wireframe; import org.andresoviedo.android_3d_model_engine.services.SceneLoader; +import org.andresoviedo.android_3d_model_engine.util.Rescaler; import org.andresoviedo.util.android.AndroidUtils; import org.andresoviedo.util.android.ContentUtils; import org.andresoviedo.util.android.GLUtil; import org.andresoviedo.util.event.EventListener; +import org.andresoviedo.util.math.Math3DUtils; +import org.andresoviedo.util.math.Quaternion; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.EventObject; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,73 +45,13 @@ public class ModelRenderer implements GLSurfaceView.Renderer { - public static class ViewEvent extends EventObject { - - private final Code code; - private final int width; - private final int height; - - public enum Code {SURFACE_CREATED, SURFACE_CHANGED} - - public ViewEvent(Object source, Code code, int width, int height) { - super(source); - this.code = code; - this.width = width; - this.height = height; - } - - public Code getCode() { - return code; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - } - - public static class FPSEvent extends EventObject { - - private final int fps; - - public FPSEvent(Object source, int fps) { - super(source); - this.fps = fps; - } - - public int getFps() { - return fps; - } - } - private final static String TAG = ModelRenderer.class.getSimpleName(); - // grid - private static final float GRID_WIDTH = 100f; - private static final float GRID_SIZE = 10f; - private static final float[] GRID_COLOR = {0.25f, 0.25f, 0.25f, 0.5f}; - // blending private static final float[] BLENDING_MASK_DEFAULT = {1.0f, 1.0f, 1.0f, 1.0f}; // Add 0.5f to the alpha component to the global shader so we can see through the skin private static final float[] BLENDING_MASK_FORCED = {1.0f, 1.0f, 1.0f, 0.5f}; - // frustrum - nearest pixel - private static final float near = 1.0f; - // frustrum - fartest pixel - private static final float far = 5000f; - - // stereoscopic variables - private static float EYE_DISTANCE = 0.64f; - private static final float[] COLOR_RED = {1.0f, 0.0f, 0.0f, 1f}; - private static final float[] COLOR_BLUE = {0.0f, 1.0f, 0.0f, 1f}; - private static final float[] COLOR_WHITE = {1f, 1f, 1f, 1f}; - private static final float[] COLOR_HALF_TRANSPARENT = {1f, 1f, 1f, 0.5f}; - private static final float[] COLOR_ALMOST_TRANSPARENT = {1f, 1f, 1f, 0.1f}; - private final float[] backgroundColor; private final SceneLoader scene; @@ -120,6 +64,8 @@ public int getFps() { // height of the screen private int height; private float ratio; + // zoom + private float zoom = 1f; /** * Drawer factory to get right renderer/shader based on object attributes @@ -150,6 +96,10 @@ public int getFps() { private final float[] viewMatrix = new float[16]; private final float[] projectionMatrix = new float[16]; private final float[] viewProjectionMatrix = new float[16]; + { + Matrix.setIdentityM(viewMatrix,0); + Matrix.setIdentityM(projectionMatrix,0); + } // light private final float[] tempVector4 = new float[4]; @@ -159,13 +109,17 @@ public int getFps() { // Decoration private final List extras = new ArrayList<>(); - private final Object3DData axis = Axis.build().setId("axis").setSolid(false).setScale(new float[]{50, 50, 50}); - private final Object3DData gridx = Grid.build(-GRID_WIDTH, 0f, -GRID_WIDTH, GRID_WIDTH, 0f, GRID_WIDTH, GRID_SIZE) - .setColor(GRID_COLOR).setId("grid-x").setSolid(false); - private final Object3DData gridy = Grid.build(-GRID_WIDTH, -GRID_WIDTH, 0f, GRID_WIDTH, GRID_WIDTH, 0f, GRID_SIZE) - .setColor(GRID_COLOR).setId("grid-y").setSolid(false); - private final Object3DData gridz = Grid.build(0, -GRID_WIDTH, -GRID_WIDTH, 0, GRID_WIDTH, GRID_WIDTH, GRID_SIZE) - .setColor(GRID_COLOR).setId("grid-z").setSolid(false); + private final Object3DData axis = Axis.build().setId("axis").setSolid(false) + .setScale(Constants.UNIT,Constants.UNIT,Constants.UNIT); + private final Object3DData gridx = Grid.build(-Constants.GRID_WIDTH, 0f, -Constants.GRID_WIDTH, Constants.GRID_WIDTH, 0f, Constants.GRID_WIDTH, Constants.GRID_SIZE) + .setColor(Constants.COLOR_RED_TRANSLUCENT).setId("grid-x").setSolid(false) + .setScale(Constants.UNIT,Constants.UNIT,Constants.UNIT);; + private final Object3DData gridy = Grid.build(-Constants.GRID_WIDTH, -Constants.GRID_WIDTH, 0f, Constants.GRID_WIDTH, Constants.GRID_WIDTH, 0f, Constants.GRID_SIZE) + .setColor(Constants.COLOR_GREEN_TRANSLUCENT).setId("grid-y").setSolid(false) + .setScale(Constants.UNIT,Constants.UNIT,Constants.UNIT);; + private final Object3DData gridz = Grid.build(0, -Constants.GRID_WIDTH, -Constants.GRID_WIDTH, 0, Constants.GRID_WIDTH, Constants.GRID_WIDTH, Constants.GRID_SIZE) + .setColor(Constants.COLOR_BLUE_TRANSLUCENT).setId("grid-z").setSolid(false) + .setScale(Constants.UNIT,Constants.UNIT,Constants.UNIT); { extras.add(axis); @@ -196,6 +150,7 @@ public int getFps() { private final float[] viewMatrixSkyBox = new float[16]; private SkyBox[] skyBoxes = null; private Object3DData[] skyBoxes3D = null; + private Quaternion orientation = new Quaternion(0,0,0,1); /** * Whether the info of the model has been written to console log @@ -205,6 +160,10 @@ public int getFps() { * Switch to akternate drawing of right and left image */ private boolean anaglyphSwitch = false; + /** + * Switch to alternate projection + */ + private Projection projection = Projection.PERSPECTIVE; /** * Skeleton Animator @@ -234,17 +193,83 @@ public ModelRenderer addListener(EventListener listener) { } public float getNear() { - return near; + return Constants.near; } public float getFar() { - return far; + return Constants.far; + } + + public void toggleProjection() { + switch (this.projection) { + case PERSPECTIVE: + setProjection(Projection.ISOMETRIC); + break; + case ISOMETRIC: + setProjection(Projection.ORTHOGRAPHIC); + break; + case ORTHOGRAPHIC: + setProjection(Projection.FREE); + break; + case FREE: + setProjection(Projection.PERSPECTIVE); + break; + } + } + + public Projection getProjection(){ + return this.projection; + } + + public void setProjection(Projection projection){ + Log.d(TAG, "setProjection: projection: [" + projection + "]"); + this.projection = projection; + + // fire event + final ViewEvent eventObject = new ViewEvent(this, ViewEvent.Code.PROJECTION_CHANGED, width, height); + eventObject.setProjection(this.projection); + AndroidUtils.fireEvent(listeners, eventObject); + } + + public void refreshMatrices(){ + + if (ratio == 0) return; + + // initialize skybox + //Matrix.frustumM(projectionMatrixSkyBox, 0, -ratio, ratio, -1, 1, 2f, getFar()); + Matrix.frustumM(projectionMatrixSkyBox, 0, -ratio, ratio, -1, 1, getNear(), getFar()); + + switch (getProjection()){ + case ORTHOGRAPHIC: + case ISOMETRIC: + Matrix.orthoM(projectionMatrix, 0, -ratio * Constants.UNIT /getZoom(), ratio* Constants.UNIT /getZoom(), -Constants.UNIT /getZoom(), Constants.UNIT /getZoom(), getNear(), getFar()); + Matrix.orthoM(projectionMatrixSkyBox, 0, -ratio * Constants.UNIT /getZoom(), ratio* Constants.UNIT /getZoom(), -Constants.UNIT /getZoom(), Constants.UNIT /getZoom(), getNear(), getFar()); + Matrix.orthoM(projectionMatrixRight, 0, -ratio * Constants.UNIT /getZoom(), ratio* Constants.UNIT /getZoom(), -Constants.UNIT /getZoom(), Constants.UNIT /getZoom(), getNear(), getFar()); + Matrix.orthoM(projectionMatrixLeft, 0, -ratio * Constants.UNIT /getZoom(), ratio* Constants.UNIT /getZoom(), -Constants.UNIT /getZoom(), Constants.UNIT /getZoom(), getNear(), getFar()); + break; + default: + // Each individual eye has a horizontal FOV of about 135 degrees + // and a vertical FOV of just over 180 degrees. + Matrix.frustumM(projectionMatrix, 0, -ratio * getNear(), ratio * getNear(), -1 * getNear(), 1 * getNear(), getNear(), getFar()); + Matrix.frustumM(projectionMatrixSkyBox, 0, -ratio * getNear(), ratio * getNear(), -1 * getNear(), 1 * getNear(), getNear(), getFar()); + Matrix.frustumM(projectionMatrixRight, 0, -ratio * getNear(), ratio * getNear(), -1 * getNear(), 1 * getNear(), getNear(), getFar()); + Matrix.frustumM(projectionMatrixLeft, 0, -ratio * getNear(), ratio * getNear(), -1 * getNear(), 1 * getNear(), getNear(), getFar()); + break; + } } public void toggleLights() { lightsEnabled = !lightsEnabled; } + public void setSkyBoxId(int skyBoxId) { + isUseskyBoxId = skyBoxId; + } + + public int getSkyBoxId(){ + return isUseskyBoxId; + } + public void toggleSkyBox() { isUseskyBoxId++; if (isUseskyBoxId > 1) { @@ -309,13 +334,11 @@ public void onSurfaceChanged(GL10 unused, int width, int height) { // the projection matrix is the 3D virtual space (cube) that we want to project this.ratio = (float) width / height; - Log.d(TAG, "onSurfaceChanged: projection: [" + -ratio + "," + ratio + ",-1,1]-near/far[1,10]"); - Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, getNear(), getFar()); - Matrix.frustumM(projectionMatrixRight, 0, -ratio, ratio, -1, 1, getNear(), getFar()); - Matrix.frustumM(projectionMatrixLeft, 0, -ratio, ratio, -1, 1, getNear(), getFar()); - Matrix.orthoM(projectionMatrixSkyBox, 0, -ratio, ratio, -1, 1, getNear(), getFar()); + // initialize projection + refreshMatrices(); + // fire event AndroidUtils.fireEvent(listeners, new ViewEvent(this, ViewEvent.Code.SURFACE_CHANGED, width, height)); } @@ -331,6 +354,8 @@ public void onDrawFrame(GL10 unused) { // Draw background color GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + GLES20.glColorMask(true, true, true, true); + GLES20.glLineWidth((float) Math.PI); if (scene == null) { // scene not ready @@ -349,6 +374,9 @@ public void onDrawFrame(GL10 unused) { GLES20.glDisable(GLES20.GL_BLEND); } + // refresh matrices + refreshMatrices(); + // animate scene scene.onDrawFrame(); @@ -358,6 +386,7 @@ public void onDrawFrame(GL10 unused) { cameraPosInWorldSpace[1] = camera.getyPos(); cameraPosInWorldSpace[2] = camera.getzPos(); if (camera.hasChanged()) { + // INFO: Set the camera position (View matrix) // The camera has 3 vectors (the position, the vector where we are looking at, and the up position (sky) @@ -365,12 +394,12 @@ public void onDrawFrame(GL10 unused) { float ratio = (float) width / height; // Log.v(TAG, "Camera changed: projection: [" + -ratio + "," + ratio + ",-1,1]-near/far[1,10], "); - if (!scene.isStereoscopic()) { - Matrix.setLookAtM(viewMatrix, 0, camera.getxPos(), camera.getyPos(), camera.getzPos(), camera.getxView(), camera.getyView(), - camera.getzView(), camera.getxUp(), camera.getyUp(), camera.getzUp()); - Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0); - } else { - Camera[] stereoCamera = camera.toStereo(EYE_DISTANCE); + Matrix.setLookAtM(viewMatrix, 0, camera.getxPos(), camera.getyPos(), camera.getzPos(), camera.getxView(), camera.getyView(), + camera.getzView(), camera.getxUp(), camera.getyUp(), camera.getzUp()); + Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0); + + if (scene.isStereoscopic()) { + Camera[] stereoCamera = camera.toStereo(Constants.EYE_DISTANCE); Camera leftCamera = stereoCamera[0]; Camera rightCamera = stereoCamera[1]; @@ -382,39 +411,49 @@ public void onDrawFrame(GL10 unused) { Matrix.setLookAtM(viewMatrixRight, 0, rightCamera.getxPos(), rightCamera.getyPos(), rightCamera.getzPos(), rightCamera .getxView(), rightCamera.getyView(), rightCamera.getzView(), rightCamera.getxUp(), rightCamera.getyUp(), rightCamera.getzUp()); - - if (scene.isAnaglyph()) { - Matrix.frustumM(projectionMatrixRight, 0, -ratio, ratio, -1, 1, getNear(), getFar()); - Matrix.frustumM(projectionMatrixLeft, 0, -ratio, ratio, -1, 1, getNear(), getFar()); - } else if (scene.isVRGlasses()) { - float ratio2 = (float) width / 2 / height; - Matrix.frustumM(projectionMatrixRight, 0, -ratio2, ratio2, -1, 1, getNear(), getFar()); - Matrix.frustumM(projectionMatrixLeft, 0, -ratio2, ratio2, -1, 1, getNear(), getFar()); - } - // Calculate the projection and view transformation - Matrix.multiplyMM(viewProjectionMatrixLeft, 0, projectionMatrixLeft, 0, viewMatrixLeft, 0); - Matrix.multiplyMM(viewProjectionMatrixRight, 0, projectionMatrixRight, 0, viewMatrixRight, 0); - } - camera.setChanged(false); + //camera.setChanged(false); } + drawSkyBox(viewMatrix, projectionMatrix, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace); + if (!scene.isStereoscopic()) { this.onDrawFrame(viewMatrix, projectionMatrix, viewProjectionMatrix, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace); + if(camera.hasChanged()) camera.setChanged(false); return; } if (scene.isAnaglyph()) { // INFO: switch because blending algorithm doesn't mix colors + int correction = -0; if (anaglyphSwitch) { + + GLES20.glColorMask(false, true, true, true); + GLES20.glViewport(-correction, 0, width-correction, height); + this.onDrawFrame(viewMatrixRight, projectionMatrixRight, viewProjectionMatrixRight, lightPosInWorldSpace, + colorMask, cameraPosInWorldSpace); + + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT); + GLES20.glColorMask(true, false, false, true); + GLES20.glViewport(correction, 0, width+correction, height); this.onDrawFrame(viewMatrixLeft, projectionMatrixLeft, viewProjectionMatrixLeft, lightPosInWorldSpace, - COLOR_RED, cameraPosInWorldSpace); + colorMask, cameraPosInWorldSpace); + } else { + + GLES20.glColorMask(true, false, false, true); + GLES20.glViewport(correction, 0, width+correction, height); + this.onDrawFrame(viewMatrixLeft, projectionMatrixLeft, viewProjectionMatrixLeft, lightPosInWorldSpace, + colorMask, cameraPosInWorldSpace); + + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT); + GLES20.glColorMask(false, true, true, true); + GLES20.glViewport(-correction, 0, width-correction, height); this.onDrawFrame(viewMatrixRight, projectionMatrixRight, viewProjectionMatrixRight, lightPosInWorldSpace, - COLOR_BLUE, cameraPosInWorldSpace); + colorMask, cameraPosInWorldSpace); } anaglyphSwitch = !anaglyphSwitch; return; @@ -442,6 +481,18 @@ public void onDrawFrame(GL10 unused) { } catch (Error err) { Log.e("ModelRenderer", "Fatal error: " + err.getMessage(), err); fatalException = true; + } finally { + if (framesPerSecondTime == -1) { + framesPerSecondTime = SystemClock.elapsedRealtime(); + framesPerSecondCounter++; + } else if (SystemClock.elapsedRealtime() > framesPerSecondTime + 1000) { + framesPerSecond = framesPerSecondCounter; + framesPerSecondCounter = 1; + framesPerSecondTime = SystemClock.elapsedRealtime(); + AndroidUtils.fireEvent(listeners, new FPSEvent(this, framesPerSecond)); + } else { + framesPerSecondCounter++; + } } } @@ -452,56 +503,6 @@ private void onDrawFrame(float[] viewMatrix, float[] projectionMatrix, float[] v // set up camera final Camera camera = scene.getCamera(); - // draw environment - int skyBoxId = isUseskyBoxId; - if (skyBoxId == -3){ - // draw all extra objects - for (int i = 0; i < extras.size(); i++) { - drawObject(viewMatrix, projectionMatrix, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace, false, false, false, false, false, extras, i); - } - } - else if (skyBoxId == -2){ - GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]); - // Draw background color - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - } - else if (skyBoxId == -1){ - // invert background color - GLES20.glClearColor(1-backgroundColor[0], 1-backgroundColor[1], 1-backgroundColor[2], 1-backgroundColor[3]); - // Draw background color - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - } - else if (isDrawSkyBox && skyBoxId >= 0 && skyBoxId < skyBoxes3D.length) { - GLES20.glDepthMask(false); - try { - //skyBoxId = 1; - // lazy building of the 3d object - if (skyBoxes3D[skyBoxId] == null) { - Log.i("ModelRenderer", "Loading sky box textures to GPU... skybox: " + skyBoxId); - int textureId = GLUtil.loadCubeMap(skyBoxes[skyBoxId].getCubeMap()); - Log.d("ModelRenderer", "Loaded textures to GPU... id: " + textureId); - if (textureId != -1) { - skyBoxes3D[skyBoxId] = SkyBox.build(skyBoxes[skyBoxId]); - } else { - Log.e("ModelRenderer", "Error loading sky box textures to GPU. "); - isDrawSkyBox = false; - } - - } - Matrix.setLookAtM(viewMatrixSkyBox, 0, 0, 0, 0, camera.getxView() - camera.getxPos(), camera.getyView() - camera.getyPos(), - camera.getzView() - camera.getzPos(), camera.getxUp() - camera.getxPos(), camera.getyUp() - camera.getyPos(), camera.getzUp() - camera.getzPos()); - if (scene.isFixCoordinateSystem()){ - Matrix.rotateM(viewMatrixSkyBox, 0, 90, 1, 0, 0); - } - Renderer basicShader = drawer.getSkyBoxDrawer(); - basicShader.draw(skyBoxes3D[skyBoxId], projectionMatrix, viewMatrixSkyBox, skyBoxes3D[skyBoxId].getMaterial().getTextureId(), null, cameraPosInWorldSpace); - } catch (Throwable ex) { - Log.e("ModelRenderer", "Error rendering sky box. " + ex.getMessage(), ex); - isDrawSkyBox = false; - } - GLES20.glDepthMask(true); - } - // draw light boolean doAnimation = scene.isDoAnimation() && animationEnabled; @@ -516,7 +517,9 @@ else if (isDrawSkyBox && skyBoxId >= 0 && skyBoxId < skyBoxes3D.length) { // Calculate position of the light in world space to support lighting if (scene.isRotatingLight()) { - Matrix.multiplyMV(tempVector4, 0, scene.getLightBulb().getModelMatrix(), 0, lightPosition, 0); + // FIXME: memory leak + Matrix.multiplyMV(tempVector4, 0, scene.getLightBulb().getModelMatrix(), 0, + Math3DUtils.to4d(scene.getLightBulb().getLocation()), 0); lightPosInWorldSpace[0] = tempVector4[0]; lightPosInWorldSpace[1] = tempVector4[1]; lightPosInWorldSpace[2] = tempVector4[2]; @@ -547,24 +550,75 @@ else if (isDrawSkyBox && skyBoxId >= 0 && skyBoxId < skyBoxes3D.length) { } // draw all GUI objects + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT); List guiObjects = scene.getGUIObjects(); for (int i = 0; i < guiObjects.size(); i++) { drawObject(viewMatrix, projectionMatrix, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace, doAnimation, drawLighting, drawWireframe, drawTextures, drawColors, guiObjects, i); } - if (framesPerSecondTime == -1) { - framesPerSecondTime = SystemClock.elapsedRealtime(); - framesPerSecondCounter++; - } else if (SystemClock.elapsedRealtime() > framesPerSecondTime + 1000) { - framesPerSecond = framesPerSecondCounter; - framesPerSecondCounter = 1; - framesPerSecondTime = SystemClock.elapsedRealtime(); - AndroidUtils.fireEvent(listeners, new FPSEvent(this, framesPerSecond)); - } else { - framesPerSecondCounter++; + debugSkeleton = !debugSkeleton; + } + + private void drawSkyBox(float[] viewMatrix, float[] projectionMatrix, float[] lightPosInWorldSpace, float[] colorMask, float[] cameraPosInWorldSpace) { + // draw environment + int skyBoxId = getSkyBoxId(); + if (skyBoxId == -3){ + GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]); + // draw all extra objects + for (int i = 0; i < extras.size(); i++) { + drawObject(viewMatrix, projectionMatrix, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace, false, false, false, false, true, extras, i); + } } + else if (skyBoxId == -2){ + GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]); + // Draw background color + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + } + else if (skyBoxId == -1){ + // invert background color + GLES20.glClearColor(1-backgroundColor[0], 1-backgroundColor[1], 1-backgroundColor[2], 1-backgroundColor[3]); + // Draw background color + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + } + else if (isDrawSkyBox && skyBoxId >= 0 && skyBoxId < skyBoxes3D.length) { + // GLES20.glDepthMask(false); + GLES20.glClearColor(0, 0, 0, 1); + try { + //skyBoxId = 1; + // lazy building of the 3d object + if (skyBoxes3D[skyBoxId] == null) { + Log.i("ModelRenderer", "Loading sky box textures to GPU... skybox: " + skyBoxId); + int textureId = GLUtil.loadCubeMap(skyBoxes[skyBoxId].getCubeMap()); + Log.d("ModelRenderer", "Loaded textures to GPU... id: " + textureId); + if (textureId != -1) { + skyBoxes3D[skyBoxId] = SkyBox.build(skyBoxes[skyBoxId]); + Rescaler.rescale(skyBoxes3D[skyBoxId], 1f); + final float scale = Constants.SKYBOX_SIZE; //getFar()/skyBoxes3D[skyBoxId].getDimensions().getLargest()/20; + skyBoxes3D[skyBoxId].setScale(scale, scale, scale); + } else { + Log.e("ModelRenderer", "Error loading sky box textures to GPU. "); + isDrawSkyBox = false; + } - debugSkeleton = !debugSkeleton; + } + Renderer basicShader = drawer.getSkyBoxDrawer(); + skyBoxes3D[skyBoxId].setColor(Constants.COLOR_BIT_TRANSPARENT); + basicShader.draw(skyBoxes3D[skyBoxId], projectionMatrixSkyBox, viewMatrix, skyBoxes3D[skyBoxId].getMaterial().getTextureId(), null, cameraPosInWorldSpace); + + // sensor stuff + /*this.orientation.toRotationMatrix(viewMatrixSkyBox); + float[] rot = new float[16]; + Matrix.setRotateM(rot,0,90,1,0,0); + float[] mat = new float[16]; + Matrix.multiplyMM(mat,0,viewMatrixSkyBox,0, rot,0); + Renderer basicShader = drawer.getSkyBoxDrawer(); + basicShader.draw(skyBoxes3D[skyBoxId], projectionMatrixSkyBox, mat, skyBoxes3D[skyBoxId].getMaterial().getTextureId(), null, cameraPosInWorldSpace);*/ + } catch (Throwable ex) { + Log.e("ModelRenderer", "Error rendering sky box. " + ex.getMessage(), ex); + isDrawSkyBox = false; + } + //GLES20.glDepthMask(true); + } } private void drawObject(float[] viewMatrix, float[] projectionMatrix, float[] lightPosInWorldSpace, float[] colorMask, float[] cameraPosInWorldSpace, boolean doAnimation, boolean drawLighting, boolean drawWireframe, boolean drawTextures, boolean drawColors, List objects, int i) { @@ -677,7 +731,7 @@ else if (scene.isDrawPoints()) { drawerObject.draw(objData, projectionMatrix, viewMatrix , GLES20.GL_POINTS, objData.getDrawSize(), textureId, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace); - objData.render(drawer, lightPosInWorldSpace, colorMask); + objData.render(drawer, scene.getCamera(), lightPosInWorldSpace, colorMask); } // draw skeleton @@ -685,7 +739,7 @@ else if (scene.isDrawSkeleton() && objData instanceof AnimatedModel && ((Animate .getAnimation() != null) { // draw the original object a bit transparent - drawerObject.draw(objData, projectionMatrix, viewMatrix, textureId, lightPosInWorldSpace, COLOR_HALF_TRANSPARENT, cameraPosInWorldSpace); + drawerObject.draw(objData, projectionMatrix, viewMatrix, textureId, lightPosInWorldSpace, Constants.COLOR_HALF_TRANSPARENT, cameraPosInWorldSpace); // draw skeleton on top of it GLES20.glDisable(GLES20.GL_DEPTH_TEST); @@ -709,7 +763,7 @@ else if (scene.isDrawSkeleton() && objData instanceof AnimatedModel && ((Animate } drawerObject.draw(objData, projectionMatrix, viewMatrix, textureId, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace); - objData.render(drawer, lightPosInWorldSpace, colorMask); + objData.render(drawer, scene.getCamera(), lightPosInWorldSpace, colorMask); } } @@ -750,13 +804,18 @@ else if (scene.isDrawSkeleton() && objData instanceof AnimatedModel && ((Animate private void drawBoundingBox(float[] viewMatrix, float[] projectionMatrix, float[] lightPosInWorldSpace, float[] colorMask, float[] cameraPosInWorldSpace, Object3DData objData, boolean changed) { Object3DData boundingBoxData = boundingBoxes.get(objData); - if (boundingBoxData == null || changed) { + if (boundingBoxData == null) { Log.i("ModelRenderer", "Building bounding box... id: " + objData.getId()); boundingBoxData = BoundingBox.build(objData); - boundingBoxData.setColor(COLOR_WHITE); + boundingBoxData.setModelMatrix(objData.getModelMatrix()); + boundingBoxData.setReadOnly(true); boundingBoxes.put(objData, boundingBoxData); Log.i("ModelRenderer", "Bounding box: " + boundingBoxData); } + boundingBoxData.setColor(Constants.COLOR_GRAY); + if(scene.getSelectedObject() == objData){ + boundingBoxData.setColor(Constants.COLOR_WHITE); + } Renderer boundingBoxDrawer = drawer.getBoundingBoxDrawer(); boundingBoxDrawer.draw(boundingBoxData, projectionMatrix, viewMatrix, -1, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace); @@ -777,4 +836,32 @@ public float[] getProjectionMatrix() { public float[] getViewMatrix() { return viewMatrix; } + + public float getZoom() { + return zoom; + } + + public void setZoom(float zoom) { + switch (projection){ + case ORTHOGRAPHIC: + case ISOMETRIC: + if (zoom > 0 && zoom < 10) { + this.zoom = zoom; + setProjection(this.projection); + } + break; + } + } + + public void addZoom(float zoom) { + this.setZoom(getZoom() + zoom); + } + + public Quaternion getOrientation() { + return orientation; + } + + public void setOrientation(Quaternion orientation) { + this.orientation = orientation; + } } \ No newline at end of file diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelSurfaceView.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelSurfaceView.java index 2c956847..0a541e20 100644 --- a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelSurfaceView.java +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ModelSurfaceView.java @@ -6,10 +6,12 @@ import android.view.MotionEvent; import android.widget.Toast; -import org.andresoviedo.android_3d_model_engine.controller.TouchController; +import org.andresoviedo.android_3d_model_engine.controller.TouchEvent; +import org.andresoviedo.android_3d_model_engine.model.Projection; import org.andresoviedo.android_3d_model_engine.services.SceneLoader; import org.andresoviedo.util.android.AndroidUtils; import org.andresoviedo.util.event.EventListener; +import org.andresoviedo.util.math.Quaternion; import java.util.ArrayList; import java.util.EventObject; @@ -25,8 +27,6 @@ public class ModelSurfaceView extends GLSurfaceView implements EventListener { private final ModelRenderer mRenderer; - private TouchController touchController; - private final List listeners = new ArrayList<>(); public ModelSurfaceView(Activity parent, float[] backgroundColor, SceneLoader scene){ @@ -48,10 +48,6 @@ public ModelSurfaceView(Activity parent, float[] backgroundColor, SceneLoader sc } } - public void setTouchController(TouchController touchController){ - this.touchController = touchController; - } - public void addListener(EventListener listener){ listeners.add(listener); } @@ -66,33 +62,52 @@ public float[] getViewMatrix() { @Override public boolean onTouchEvent(MotionEvent event) { - try { - return touchController.onTouchEvent(event); - } catch (Exception ex) { - Log.e("ModelSurfaceView","Exception: "+ ex.getMessage(),ex); - } - return false; + // propagate event to responsible... + AndroidUtils.fireEvent(listeners, new EventObject(event)); + return true; } - public ModelRenderer getModelRenderer() { - return mRenderer; - } - private void fireEvent(EventObject event) { - AndroidUtils.fireEvent(listeners,event); - } @Override public boolean onEvent(EventObject event) { - fireEvent(event); + if (event instanceof TouchEvent && ((TouchEvent) event).getAction() == TouchEvent.Action.PINCH){ + mRenderer.addZoom(-mRenderer.getZoom() * ((TouchEvent) event).getZoom() / 100f); + } else { + AndroidUtils.fireEvent(listeners, event); + } return true; } + public void toggleProjection() { + Log.i("ModelSurfaceView","Toggling projection..."); + mRenderer.toggleProjection(); + Toast.makeText(getContext(), "Projection: "+mRenderer.getProjection(), Toast.LENGTH_SHORT).show(); + } + + + public void setProjection(Projection projection) { + mRenderer.setProjection(projection); + } + + public Projection getProjection(){ + return mRenderer.getProjection(); + } + public void toggleLights() { Log.i("ModelSurfaceView","Toggling lights..."); mRenderer.toggleLights(); } + public int getSkyBoxId(){ + return mRenderer.getSkyBoxId(); + } + + public void setSkyBox(int skyBoxId) { + mRenderer.setSkyBoxId(skyBoxId); + } + + public void toggleSkyBox() { Log.i("ModelSurfaceView","Toggling sky box..."); mRenderer.toggleSkyBox(); @@ -122,4 +137,7 @@ public boolean isLightsEnabled() { return mRenderer.isLightsEnabled(); } + public void setOrientation(Quaternion orientation) { + mRenderer.setOrientation(orientation); + } } diff --git a/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ViewEvent.java b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ViewEvent.java new file mode 100644 index 00000000..1f5aefd3 --- /dev/null +++ b/engine/src/main/java/org/andresoviedo/android_3d_model_engine/view/ViewEvent.java @@ -0,0 +1,55 @@ +package org.andresoviedo.android_3d_model_engine.view; + +import org.andresoviedo.android_3d_model_engine.model.Projection; + +import java.util.EventObject; + +public class ViewEvent extends EventObject { + + private final Code code; + + private int width; + private int height; + private Projection projection; + + public enum Code {SURFACE_CREATED, SURFACE_CHANGED, PROJECTION_CHANGED} + + public ViewEvent(Object source, Code code) { + super(source); + this.code = code; + } + + public ViewEvent(Object source, Code code, int width, int height) { + super(source); + this.code = code; + this.width = width; + this.height = height; + } + + public Code getCode() { + return code; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public Projection getProjection() { + return projection; + } + + public void setProjection(Projection projection) { + this.projection = projection; + } + + @Override + public String toString() { + return "ViewEvent{" + + "code=" + code + + '}'; + } +} diff --git a/engine/src/main/java/org/andresoviedo/util/android/AndroidUtils.java b/engine/src/main/java/org/andresoviedo/util/android/AndroidUtils.java index 01dfb61a..55f4edab 100644 --- a/engine/src/main/java/org/andresoviedo/util/android/AndroidUtils.java +++ b/engine/src/main/java/org/andresoviedo/util/android/AndroidUtils.java @@ -24,7 +24,11 @@ public interface Callback { } public static void fireEvent(List listeners, EventObject eventObject){ - for (int i=0; itrue if the event should be further processed by other listeners, false otherwise + */ boolean onEvent(EventObject event); } diff --git a/engine/src/main/java/org/andresoviedo/util/math/Math3DUtils.java b/engine/src/main/java/org/andresoviedo/util/math/Math3DUtils.java index 6b519794..47936099 100644 --- a/engine/src/main/java/org/andresoviedo/util/math/Math3DUtils.java +++ b/engine/src/main/java/org/andresoviedo/util/math/Math3DUtils.java @@ -3,6 +3,7 @@ import android.opengl.Matrix; import org.andresoviedo.android_3d_model_engine.animation.JointTransform; +import org.andresoviedo.android_3d_model_engine.model.Constants; import java.math.BigDecimal; import java.util.List; @@ -16,6 +17,9 @@ public class Math3DUtils { public static final float IDENTITY_MATRIX[] = new float[16]; + public static final float VECTOR_UNIT_X[] = {1,0,0}; + public static final float VECTOR_UNIT_Y[] = {0,1,0}; + public static final float VECTOR_UNIT_Z[] = {0,0,1}; static { Matrix.setIdentityM(IDENTITY_MATRIX, 0); @@ -323,6 +327,12 @@ public static void normalize(float[] a) { a[2] = a[2] / length; } + public static float[] normalize2(float[] a) { + float[] copy = a.clone(); + normalize(copy); + return copy; + } + public static float[] crossProduct(float[] a, float[] b) { // AxB = (AyBz − AzBy, AzBx − AxBz, AxBy − AyBx) //(r)[0] = (a)[1] * (b)[2] - (b)[1] * (a)[2]; \ @@ -350,6 +360,10 @@ public static float dotProduct(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } + public static void mult(float[] v, float t) { + for (int i = 0; i < v.length; i++) v[i] = v[i] * t; + } + public static float[] multiply(float[] a, float t) { return new float[]{a[0] * t, a[1] * t, a[2] * t}; } @@ -609,5 +623,99 @@ public static float[] createRotationMatrixAroundVector(float angle, float x, flo return matrix; } + + /** + * Calculate the angle between the 2 specified vectors + * + * @param v1 + * @param v2 + * @return + */ + public static double calculateAngleBetween(float[] v1, float[] v2){ + // Using acos + //float[] v1n = normalize2(v1); + //float[] v2n = normalize2(v2); + + // perp dot-product + // https://stackoverflow.com/questions/2150050/finding-signed-angle-between-vectors + //return Math.atan2( v1n[0]*v2n[1] - v1n[1]*v2n[0], v1n[0]*v2n[0] + v1n[1]*v2n[1] ); + return Math.atan2( v1[0]*v2[1] - v1[1]*v2[0], v1[0]*v2[0] + v1[1]*v2[1] ); + + } + + public static void createRotationMatrixAroundVector(float[] matrix, int offset, double angle, float[] vector) { + createRotationMatrixAroundVector(matrix, offset, angle, vector[0], vector[1], vector[2]); + } + + /** + * + * @param matrix output matrix + * @param offset output matrix offset + * @param angle in radians + * @param x rotation vector x + * @param y rotation vector y + * @param z rotation vector z + */ + public static void createRotationMatrixAroundVector(float[] matrix, int offset, double angle, float x, float y, + float z) { + float cos = (float) Math.cos(angle); + float sin = (float) Math.sin(angle); + float cos_1 = 1 - cos; + + // @formatter:off + matrix[offset] = cos_1 * x * x + cos; + matrix[offset + 1] = cos_1 * x * y - z * sin; + matrix[offset + 2] = cos_1 * z * x + y * sin; + matrix[offset + 3] = 0; + matrix[offset + 4] = cos_1 * x * y + z * sin; + matrix[offset + 5] = cos_1 * y * y + cos; + matrix[offset + 6] = cos_1 * y * z - x * sin; + matrix[offset + 7] = 0; + matrix[offset + 8] = cos_1 * z * x - y * sin; + matrix[offset + 9] = cos_1 * y * z + x * sin; + matrix[offset + 10] = cos_1 * z * z + cos; + matrix[offset + 11] = 0; + matrix[offset + 12] = 0; + matrix[offset + 13] = 0; + matrix[offset + 14] = 0; + matrix[offset + 15] = 1; + + // @formatter:on + } + + public static void multiplyMMV(float[] result, int retOffset, float[] matrix, int matOffet, float[] vector4Matrix, + int vecOffset) { + for (int i = 0; i < vector4Matrix.length / 4; i++) { + Matrix.multiplyMV(result, retOffset + (i * 4), matrix, matOffet, vector4Matrix, vecOffset + (i * 4)); + } + } + + public static void snapToGrid(float[] v) { + final float[] TEST_VALUES = { + Constants.UNIT_SIN_1, Constants.UNIT_SIN_2, Constants.UNIT_SIN_3, Constants.UNIT_SIN_5, + Constants.UNIT_SIN_1 * Constants.UNIT, Constants.UNIT_SIN_2 * Constants.UNIT, + Constants.UNIT_SIN_3 * Constants.UNIT, Constants.UNIT_SIN_5 * Constants.UNIT, + Constants.UNIT_0, Constants.UNIT_1, Constants.UNIT_2, Constants.UNIT_3, Constants.UNIT_5, + Constants.UNIT_0 * Constants.UNIT, Constants.UNIT_1 * Constants.UNIT, Constants.UNIT_2 * Constants.UNIT, + Constants.UNIT_3 * Constants.UNIT, Constants.UNIT_5 * Constants.UNIT}; + for (int i = 0; i < v.length; i++) { + for (int j = 0; j < TEST_VALUES.length; j++) { + final float testValue = TEST_VALUES[j] ; + if (v[i] >= (-testValue - Constants.SNAP_TO_GRID_THRESHOLD) + && v[i] <= (-testValue + Constants.SNAP_TO_GRID_THRESHOLD)) { + v[i] = -testValue; + break; + } else if (v[i] >= (testValue - Constants.SNAP_TO_GRID_THRESHOLD) + && v[i] <= (testValue + Constants.SNAP_TO_GRID_THRESHOLD)) { + v[i] = testValue; + break; + } + } + } + } + + public static float[] to4d(float[] v3d) { + return new float[]{v3d[0], v3d[1], v3d[2], 1}; + } } diff --git a/engine/src/main/java/org/andresoviedo/util/math/Quaternion.java b/engine/src/main/java/org/andresoviedo/util/math/Quaternion.java index 16a14256..fefd8723 100644 --- a/engine/src/main/java/org/andresoviedo/util/math/Quaternion.java +++ b/engine/src/main/java/org/andresoviedo/util/math/Quaternion.java @@ -21,8 +21,13 @@ */ public class Quaternion { + private float[] matrix; private float x, y, z, w; + public Quaternion(float[] matrix){ + this.matrix = matrix; + } + public Quaternion() { this(0,0,0,1); } @@ -67,6 +72,11 @@ public void normalize() { * this quaternion. */ public float[] toRotationMatrix(float[] matrix) { + if (this.matrix != null){ + System.arraycopy(this.matrix,0,matrix,0,matrix.length); + return matrix; + } + final float xy = x * y; final float xz = x * z; final float xw = x * w; @@ -222,4 +232,106 @@ public float[] toEuler() { return new float[]{roll, pitch, yaw, 1}; } + + /** + * Returns the conjugate quaternion of the instance. + * + * @return the conjugate quaternion + */ + public Quaternion getConjugate() { + return new Quaternion(-this.x, -this.y, -this.z, this.w); + } + + /** + * Returns the Hamilton product of two quaternions. + * + * @param q1 First quaternion. + * @param q2 Second quaternion. + * @return the product {@code q1} and {@code q2}, in that order. + */ + public static Quaternion multiply(final Quaternion q1, final Quaternion q2) { + // Components of the first quaternion. + final double q1a = q1.getW(); + final double q1b = q1.getX(); + final double q1c = q1.getY(); + final double q1d = q1.getZ(); + + // Components of the second quaternion. + final double q2a = q2.getW(); + final double q2b = q2.getX(); + final double q2c = q2.getY(); + final double q2d = q2.getZ(); + + // Components of the product. + final double w = q1a * q2a - q1b * q2b - q1c * q2c - q1d * q2d; + final double x = q1a * q2b + q1b * q2a + q1c * q2d - q1d * q2c; + final double y = q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b; + final double z = q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a; + + return new Quaternion((float)x, (float)y, (float)z, (float)w); + } + + public static Quaternion getQuaternion(float[] axis, float angle) { + float w = (float) Math.cos(angle / 2f); + float x = (float) (axis[0] * Math.sin(angle / 2f)); + float y = (float) (axis[1] * Math.sin(angle / 2f)); + float z = (float) (axis[2] * Math.sin(angle / 2f)); + return new Quaternion(x,y,z,w); + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + public float getW() { + return w; + } + + public float[] toAxisAngle(){ + if (w > 1) normalize(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised + + double angle = 2 * Math.acos(w); + + float[] ret = new float[]{x,y,z, (float) Math.toDegrees(angle)}; + + double s = Math.sqrt(1-w*w); // assuming quaternion normalised then w is less than 1, so term always positive. + if (s >= 0.001) { // test to avoid divide by zero, s is always positive due to sqrt + Math3DUtils.normalize(ret); + } else { + // if s close to zero then direction of axis not important + //x = q1.x; // if it is important that axis is normalised then replace with x=1; y=z=0; + //y = q1.y; + //z = q1.z; + } + return ret; + } + + public float getAngle(){ + return (float) (2*Math.acos(w)); + } + + public Quaternion getInverse() { + return new Quaternion(-this.x, -this.y, -this.z, this.w); + } + } diff --git a/engine/src/main/res/raw/shader_skybox_frag b/engine/src/main/res/raw/shader_skybox_frag index d257129b..256d53c5 100644 --- a/engine/src/main/res/raw/shader_skybox_frag +++ b/engine/src/main/res/raw/shader_skybox_frag @@ -10,5 +10,5 @@ varying vec4 v_TexCoordinate; void main(){ gl_FragColor = textureCube(u_TextureCube,v_TexCoordinate.xyz); - //gl_FragColor = vColor * vColorMask; + gl_FragColor = gl_FragColor * vColor * vColorMask; } \ No newline at end of file diff --git a/engine/src/main/res/raw/shader_skybox_vert b/engine/src/main/res/raw/shader_skybox_vert index 7379f76e..a04c4c60 100644 --- a/engine/src/main/res/raw/shader_skybox_vert +++ b/engine/src/main/res/raw/shader_skybox_vert @@ -1,5 +1,5 @@ // MVP matrices -// uniform mat4 u_MMatrix; +uniform mat4 u_MMatrix; uniform mat4 u_VMatrix; uniform mat4 u_PMatrix; @@ -16,12 +16,11 @@ varying vec4 v_TexCoordinate; void main(){ // calculate MVP matrix - // mat4 u_MVMatrix = u_VMatrix * u_MMatrix; - mat4 u_VPMatrix = u_PMatrix * u_VMatrix; + mat4 u_MVMatrix = u_VMatrix * u_MMatrix; + mat4 u_MVPMatrix = u_PMatrix * u_MVMatrix; // calculate rendered position - gl_Position = u_VPMatrix * a_Position; - // gl_PointSize = 15.0; + gl_Position = u_MVPMatrix * a_Position; // colors v_TexCoordinate = a_Position;