/*
* Flingbox - An OpenSource physics sandbox for Google's Android
* Copyright (C) 2009 Jon Ander Peñalba & Endika Gutiérrez
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.eside.flingbox.scene;
import java.io.IOException;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.Activity;
import android.content.Context;
import android.hardware.SensorManager;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import edu.eside.flingbox.BodySettingsDialog;
import edu.eside.flingbox.Preferences;
import edu.eside.flingbox.graphics.SceneRenderer;
import edu.eside.flingbox.graphics.RenderCamera;
import edu.eside.flingbox.input.SceneGestureDetector;
import edu.eside.flingbox.input.SceneGestureDetector.OnInputListener;
import edu.eside.flingbox.bodies.Body;
import edu.eside.flingbox.bodies.Polygon;
import edu.eside.flingbox.math.Vector2D;
import edu.eside.flingbox.physics.PhysicBody;
import edu.eside.flingbox.physics.ScenePhysics;
import edu.eside.flingbox.physics.gravity.GravitySource;
import edu.eside.flingbox.xml.InvalidXmlException;
import edu.eside.flingbox.xml.XmlExporter.XmlSerializable;
import edu.eside.flingbox.xml.XmlImporter.XmlParseable;
public class Scene {
private float mDisplayWidth = 320f;
// private float mDisplayHeight = 480f; // We don't need this
// Vibrator instance
private Vibrator mVibrator;
private RenderCamera mCamera;
private final SceneRenderer mSceneRenderer;
private final ScenePhysics mScenePhysics;
private final SceneGestureDetector mGestureDetector;
private final ArrayList<Body> mOnSceneBodies = new ArrayList<Body>();
private final Context mContext;
private Body mSelectedBody = null;
private boolean mIsDraggingBody = false;
private DrawingBody mDrawingBody;
public final static int SCENE_MODE_PREVIEW = 0;
public final static int SCENE_MODE_DRAWING = 10;
public final static int SCENE_MODE_DRAWING_POLYGON = 10;
public final static int SCENE_MODE_DRAWING_BOX = 11;
public final static int SCENE_MODE_DRAWING_GROUND = 12;
private int mMode = SCENE_MODE_PREVIEW;
public Scene(Context c) {
mContext = c;
GravitySource gravity;
if (Preferences.useAcelerometerBasedGravity)
try {
gravity = GravitySource.getAccelerometerBasedGravity(c);
} catch (Exception ex) {
/* We don't have accelerometers */
gravity = GravitySource.getStaticGravity(0f,
-SensorManager.GRAVITY_EARTH);
}
else
gravity = GravitySource.getStaticGravity(0f,
-SensorManager.GRAVITY_EARTH);
mSceneRenderer = new SceneRenderer();
mScenePhysics = new ScenePhysics(gravity);
mGestureDetector = new SceneGestureDetector(c, mInputListener);
mCamera = mSceneRenderer.getCamera();
mVibrator = (Vibrator) c.getSystemService(Context.VIBRATOR_SERVICE);
if (c instanceof Activity) {
DisplayMetrics dm = new DisplayMetrics();
((Activity) c).getWindowManager().getDefaultDisplay()
.getMetrics(dm);
// mDisplayHeight = dm.heightPixels;
mDisplayWidth = dm.widthPixels;
}
System.gc(); // This is a good moment to call to Garbage Collector.
}
public ScenePhysics getScenePhysics() {
return mScenePhysics;
}
public void setSceneMode(int mode) {
switch (mode) {
case SCENE_MODE_PREVIEW:
mDrawingBody = null;
break;
case SCENE_MODE_DRAWING_POLYGON:
mDrawingBody = new DrawingPolygon();
break;
}
mMode = mode;
}
public int getSceneMode() {
return mMode;
}
public void onZoom(final float scale) {
mCamera.setAperture(mCamera.getAperture().mul(1 / scale));
}
public void onMoveCamera(final Vector2D distance) {
mCamera.setPosition(mCamera.getPosition().add(distance));
}
public void add(Body body) {
mOnSceneBodies.add(body);
mSceneRenderer.add(body.getRender());
mScenePhysics.add(body.getPhysics());
}
public boolean remove(Body body) {
boolean removed = mOnSceneBodies.remove(body);
removed &= mSceneRenderer.remove(body.getRender());
removed &= mScenePhysics.remove(body.getPhysics());
return removed;
}
/**
* Clear all bodys on scene
*/
public void clearScene() {
final ArrayList<Body> bodies = mOnSceneBodies;
mScenePhysics.stopSimulation();
while (!bodies.isEmpty())
remove(bodies.get(0));
}
/**
* @return Renderer for the scene
*/
public Renderer getSceneRenderer() {
return mSceneRenderer;
}
/**
* Must be called when trackball event occurs
*/
public boolean onTrackballEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mScenePhysics.isSimulating())
mScenePhysics.stopSimulation();
else
mScenePhysics.startSimulation();
return true;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
/**
* Must be called when touch event occurs
* @param ev event
* @return true if event caugth
*/
public boolean onTouchEvent(MotionEvent ev) {
return mGestureDetector.onTouchEvent(ev);
}
/**
* Touch input callbacks
*/
private OnInputListener mInputListener = new OnInputListener() {
/**
*
*/
public boolean onDown(MotionEvent e) {
final Vector2D p = mCamera
.project(new Vector2D(e.getX(), e.getY()));
// Check if user is dragging body
for (Body object : mOnSceneBodies)
if (object.getPhysics().contains(p)) {
mSelectedBody = object;
mIsDraggingBody = true;
return true;
}
switch (mMode) {
case SCENE_MODE_PREVIEW:
// Nothing to Do
break;
case SCENE_MODE_DRAWING:
mSceneRenderer.add(mDrawingBody.getDrawingRender());
mDrawingBody.newDrawingPoint(p);
break;
}
return true;
}
@Override
public boolean onScroll(MotionEvent downEv, MotionEvent e,
float distanceX, float distanceY) {
if (mIsDraggingBody) {
final PhysicBody selectedPhysics = mSelectedBody.getPhysics();
final Vector2D touchPosition = mCamera.project(new Vector2D(e
.getX(), e.getY()));
if (mScenePhysics.isSimulating()) {
Vector2D downPosition = mCamera.project(new Vector2D(downEv
.getX(), downEv.getY()));
/* Apply force to the position */
Vector2D movementImpulse = touchPosition.sub(downPosition)
.mul(selectedPhysics.getBodyMass());
selectedPhysics.applyImpulse(movementImpulse);
} else
/* Just move the body */
selectedPhysics.setPosition(touchPosition);
}
switch (mMode) {
case SCENE_MODE_PREVIEW:
final Vector2D distance = mCamera.scale(new Vector2D(distanceX,
distanceY));
mCamera.setPosition(mCamera.getPosition().add(distance));
break;
case SCENE_MODE_DRAWING:
final Vector2D p = mCamera.project(new Vector2D(e.getX(), e
.getY()));
mDrawingBody.newDrawingPoint(p);
break;
}
return true;
}
/**
*
*/
@Override
public boolean onUp(MotionEvent e) {
if (mIsDraggingBody) {
mSelectedBody = null;
mIsDraggingBody = false;
}
switch (mMode) {
case SCENE_MODE_PREVIEW:
break;
case SCENE_MODE_DRAWING:
mSceneRenderer.remove(mDrawingBody.getDrawingRender());
final Body drawedBody = mDrawingBody.finalizeDrawing();
if (drawedBody != null)
add(drawedBody);
System.gc();
break;
}
return true;
}
/**
* Called when fling occurs
*/
public boolean onFling(MotionEvent onDownEv, MotionEvent e,
float velocityX, float velocityY) {
boolean handled = false;
if (mSelectedBody != null) {
final float cameraScale = mCamera.getAperture().i
/ mDisplayWidth;
final float vx = (velocityX * cameraScale);
final float vy = (velocityY * cameraScale);
mSelectedBody.getPhysics().setVelocity(vx, vy);
handled = true;
}
return handled;
}
/**
*
*/
public void onLongPress(MotionEvent e) {
if (mSelectedBody != null) {
mVibrator.vibrate(50); // vibrate as haptic feedback
BodySettingsDialog dialog = new BodySettingsDialog(mContext,
mSelectedBody, Scene.this);
dialog.show();
}
}
@Override
public boolean onPinch(MotionEvent ev1, MotionEvent ev2, float scale,
float dX, float dY) {
final Vector2D distance = mCamera.scale(new Vector2D(dX, dY));
onMoveCamera(distance);
onZoom(scale);
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
};
private final XmlSerializable mSerializer = new XmlSerializable() {
private final static String TAG_FLINGBOX = "flingbox";
/**
* Scene exporter
*/
@Override
public boolean writeXml(XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException,
IOException {
boolean writeSuccess = true;
serializer.startTag("", TAG_FLINGBOX);
for (Body body : mOnSceneBodies)
writeSuccess &= body.writeXml(serializer);
serializer.endTag("", TAG_FLINGBOX);
return writeSuccess;
}
};
private final XmlParseable mParser = new XmlParseable() {
private final static String TAG_FLINGBOX = "flingbox";
private final static String TAG_POLYGON = "polygon";
/**
* Scene importer
*/
@Override
public boolean readXml(XmlPullParser parser)
throws XmlPullParserException, IOException, InvalidXmlException {
boolean readSuccess = true;
if ((parser.getEventType() != XmlPullParser.START_TAG)
|| !(parser.getName().equals(TAG_FLINGBOX)))
throw new InvalidXmlException("Scene start tag expected but "
+ parser.getName() + " found.");
for (int eventType = parser.next(); eventType != XmlPullParser.END_TAG; eventType = parser
.next()) {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(TAG_POLYGON)) {
Body newBody = new Polygon();
newBody.readXml(parser);
add(newBody);
} else
throw new InvalidXmlException(
"Any body start tag expected but "
+ parser.getName() + " found.");
} else
throw new InvalidXmlException("Start tag expected but "
+ parser.getName() + " found.");
}
if (!parser.getName().equals(TAG_FLINGBOX))
throw new InvalidXmlException("Scene end tag expected but "
+ parser.getName() + " found.");
;
return readSuccess;
}
};
public XmlSerializable getSerializer() {
return mSerializer;
}
public XmlParseable getParser() {
return mParser;
}
}