package com.asha.vrlib;
import android.app.Activity;
import android.content.Context;
import android.graphics.RectF;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.widget.Toast;
import com.asha.vrlib.common.GLUtil;
import com.asha.vrlib.common.MDGLHandler;
import com.asha.vrlib.common.MDMainHandler;
import com.asha.vrlib.compact.CompactEyePickAdapter;
import com.asha.vrlib.compact.CompactTouchPickAdapter;
import com.asha.vrlib.model.BarrelDistortionConfig;
import com.asha.vrlib.model.MDDirectorBrief;
import com.asha.vrlib.model.MDHitEvent;
import com.asha.vrlib.model.MDMainPluginBuilder;
import com.asha.vrlib.model.MDPinchConfig;
import com.asha.vrlib.model.MDRay;
import com.asha.vrlib.plugins.hotspot.IMDHotspot;
import com.asha.vrlib.plugins.MDAbsPlugin;
import com.asha.vrlib.plugins.MDPluginManager;
import com.asha.vrlib.plugins.hotspot.MDAbsView;
import com.asha.vrlib.strategy.display.DisplayModeManager;
import com.asha.vrlib.strategy.interactive.InteractiveModeManager;
import com.asha.vrlib.strategy.projection.IMDProjectionFactory;
import com.asha.vrlib.strategy.projection.ProjectionModeManager;
import com.asha.vrlib.texture.MD360BitmapTexture;
import com.asha.vrlib.texture.MD360Texture;
import com.asha.vrlib.texture.MD360VideoTexture;
import com.google.android.apps.muzei.render.GLTextureView;
import java.util.Iterator;
import java.util.List;
import static com.asha.vrlib.common.VRUtil.notNull;
/**
* Created by hzqiujiadi on 16/3/12.
* hzqiujiadi ashqalcn@gmail.com
*/
public class MDVRLibrary {
private static final String TAG = "MDVRLibrary";
public static final int sMultiScreenSize = 2;
// interactive mode
public static final int INTERACTIVE_MODE_MOTION = 1;
public static final int INTERACTIVE_MODE_TOUCH = 2;
public static final int INTERACTIVE_MODE_MOTION_WITH_TOUCH = 3;
public static final int INTERACTIVE_MODE_CARDBORAD_MOTION = 4;
public static final int INTERACTIVE_MODE_CARDBORAD_MOTION_WITH_TOUCH = 5;
// display mode
public static final int DISPLAY_MODE_NORMAL = 101;
public static final int DISPLAY_MODE_GLASS = 102;
// projection mode
public static final int PROJECTION_MODE_SPHERE = 201;
public static final int PROJECTION_MODE_DOME180 = 202;
public static final int PROJECTION_MODE_DOME230 = 203;
public static final int PROJECTION_MODE_DOME180_UPPER = 204;
public static final int PROJECTION_MODE_DOME230_UPPER = 205;
/**
* @deprecated since 2.0.4
* use {@link #PROJECTION_MODE_STEREO_SPHERE_VERTICAL}
*/
@Deprecated public static final int PROJECTION_MODE_STEREO_SPHERE = 206;
public static final int PROJECTION_MODE_PLANE_FIT = 207;
public static final int PROJECTION_MODE_PLANE_CROP = 208;
public static final int PROJECTION_MODE_PLANE_FULL = 209;
public static final int PROJECTION_MODE_MULTI_FISH_EYE_HORIZONTAL = 210;
public static final int PROJECTION_MODE_MULTI_FISH_EYE_VERTICAL = 211;
public static final int PROJECTION_MODE_STEREO_SPHERE_HORIZONTAL = 212;
public static final int PROJECTION_MODE_STEREO_SPHERE_VERTICAL = 213;
private RectF mTextureSize = new RectF(0, 0, 1024, 1024);
private InteractiveModeManager mInteractiveModeManager;
private DisplayModeManager mDisplayModeManager;
private ProjectionModeManager mProjectionModeManager;
private MDPluginManager mPluginManager;
private MDPickerManager mPickerManager;
private MDGLScreenWrapper mScreenWrapper;
private MDTouchHelper mTouchHelper;
private MD360Texture mTexture;
private MDGLHandler mGLHandler;
private MDDirectorCamUpdate mDirectorCameraUpdate;
private MDDirectorFilter mDirectorFilter;
private MDVRLibrary(Builder builder) {
// init main handler
MDMainHandler.init();
// init gl handler
mGLHandler = new MDGLHandler();
// init mode manager
initModeManager(builder);
// init plugin manager
initPluginManager(builder);
// init glSurfaceViews
initOpenGL(builder.activity, builder.screenWrapper);
mTexture = builder.texture;
mTouchHelper = new MDTouchHelper(builder.activity);
mTouchHelper.addClickListener(builder.gestureListener);
mTouchHelper.setPinchEnabled(builder.pinchEnabled);
final UpdatePinchRunnable updatePinchRunnable = new UpdatePinchRunnable();
mTouchHelper.setAdvanceGestureListener(new IAdvanceGestureListener() {
@Override
public void onDrag(float distanceX, float distanceY) {
mInteractiveModeManager.handleDrag((int) distanceX,(int) distanceY);
}
@Override
public void onPinch(final float scale) {
updatePinchRunnable.setScale(scale);
mGLHandler.post(updatePinchRunnable);
}
});
mTouchHelper.setPinchConfig(builder.pinchConfig);
mScreenWrapper.getView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mTouchHelper.handleTouchEvent(event);
}
});
// init picker manager
initPickerManager(builder);
// add plugin
initPlugin();
}
private class UpdatePinchRunnable implements Runnable{
private float scale;
public void setScale(float scale) {
this.scale = scale;
}
@Override
public void run() {
List<MD360Director> directors = mProjectionModeManager.getDirectors();
for (MD360Director director : directors){
director.setNearScale(scale);
}
}
}
private void initModeManager(Builder builder) {
// init director camera update
mDirectorCameraUpdate = new MDDirectorCamUpdate();
// init director
mDirectorFilter = new MDDirectorFilter();
mDirectorFilter.setDelegate(builder.directorFilter);
// init ProjectionModeManager
ProjectionModeManager.Params projectionManagerParams = new ProjectionModeManager.Params();
projectionManagerParams.textureSize = mTextureSize;
projectionManagerParams.directorFactory = builder.directorFactory;
projectionManagerParams.projectionFactory = builder.projectionFactory;
projectionManagerParams.mainPluginBuilder = new MDMainPluginBuilder()
.setCameraUpdate(mDirectorCameraUpdate)
.setFilter(mDirectorFilter)
.setContentType(builder.contentType)
.setTexture(builder.texture);
mProjectionModeManager = new ProjectionModeManager(builder.projectionMode, mGLHandler, projectionManagerParams);
mProjectionModeManager.prepare(builder.activity, builder.notSupportCallback);
// init DisplayModeManager
mDisplayModeManager = new DisplayModeManager(builder.displayMode, mGLHandler);
mDisplayModeManager.setBarrelDistortionConfig(builder.barrelDistortionConfig);
mDisplayModeManager.setAntiDistortionEnabled(builder.barrelDistortionConfig.isDefaultEnabled());
mDisplayModeManager.prepare(builder.activity, builder.notSupportCallback);
// init InteractiveModeManager
InteractiveModeManager.Params interactiveManagerParams = new InteractiveModeManager.Params();
interactiveManagerParams.projectionModeManager = mProjectionModeManager;
interactiveManagerParams.mMotionDelay = builder.motionDelay;
interactiveManagerParams.mSensorListener = builder.sensorListener;
mInteractiveModeManager = new InteractiveModeManager(builder.interactiveMode, mGLHandler, interactiveManagerParams);
mInteractiveModeManager.prepare(builder.activity, builder.notSupportCallback);
}
private void initPluginManager(Builder builder) {
mPluginManager = new MDPluginManager();
}
private void initPickerManager(Builder builder) {
mPickerManager = MDPickerManager.with()
.setPluginManager(mPluginManager)
.setDisplayModeManager(mDisplayModeManager)
.setProjectionModeManager(mProjectionModeManager)
.build();
setEyePickEnable(builder.eyePickEnabled);
mPickerManager.setEyePickChangedListener(builder.eyePickChangedListener);
mPickerManager.setTouchPickListener(builder.touchPickChangedListener);
// listener
mTouchHelper.addClickListener(mPickerManager.getTouchPicker());
}
private void initOpenGL(Context context, MDGLScreenWrapper screenWrapper) {
if (GLUtil.supportsEs2(context)) {
screenWrapper.init(context);
// Request an OpenGL ES 2.0 compatible context.
MD360Renderer renderer = MD360Renderer.with(context)
.setGLHandler(mGLHandler)
.setPluginManager(mPluginManager)
.setProjectionModeManager(mProjectionModeManager)
.setDisplayModeManager(mDisplayModeManager)
.build();
// Set the renderer to our demo renderer, defined below.
screenWrapper.setRenderer(renderer);
this.mScreenWrapper = screenWrapper;
} else {
this.mScreenWrapper.getView().setVisibility(View.GONE);
Toast.makeText(context, "OpenGLES2 not supported.", Toast.LENGTH_SHORT).show();
}
}
private void initPlugin() {
addPlugin(mProjectionModeManager.getDirectorUpdatePlugin());
addPlugin(mPickerManager.getEyePicker());
}
public MDDirectorCamUpdate updateCamera(){
return mDirectorCameraUpdate;
}
public MDDirectorBrief getDirectorBrief(){
return mProjectionModeManager.getDirectorBrief();
}
public void switchInteractiveMode(final Activity activity) {
mInteractiveModeManager.switchMode(activity);
}
/**
* Switch Interactive Mode
*
* @param activity activity
* @param mode mode
*
* {@link #INTERACTIVE_MODE_MOTION}
* {@link #INTERACTIVE_MODE_TOUCH}
* {@link #INTERACTIVE_MODE_MOTION_WITH_TOUCH}
*/
public void switchInteractiveMode(final Activity activity, final int mode){
mInteractiveModeManager.switchMode(activity, mode);
}
public void switchDisplayMode(final Activity activity){
mDisplayModeManager.switchMode(activity);
}
/**
* Switch Display Mode
*
* @param activity activity
* @param mode mode
*
* {@link #DISPLAY_MODE_GLASS}
* {@link #DISPLAY_MODE_NORMAL}
*/
public void switchDisplayMode(final Activity activity, final int mode){
mDisplayModeManager.switchMode(activity, mode);
}
/**
* Switch Projection Mode
*
* @param activity activity
* @param mode mode
*
* {@link #PROJECTION_MODE_SPHERE}
* {@link #PROJECTION_MODE_DOME180}
* and so on.
*/
public void switchProjectionMode(final Activity activity, final int mode) {
mProjectionModeManager.switchMode(activity, mode);
}
public void resetTouch(){
mGLHandler.post(new Runnable() {
@Override
public void run() {
List<MD360Director> directors = mProjectionModeManager.getDirectors();
for (MD360Director director : directors){
director.reset();
}
}
});
}
public void resetPinch(){
mTouchHelper.reset();
}
public void resetEyePick(){
mPickerManager.resetEyePick();
}
public void setAntiDistortionEnabled(boolean enabled){
mDisplayModeManager.setAntiDistortionEnabled(enabled);
}
public boolean isAntiDistortionEnabled(){
return mDisplayModeManager.isAntiDistortionEnabled();
}
public boolean isEyePickEnable() {
return mPickerManager.isEyePickEnable();
}
public void setEyePickEnable(boolean eyePickEnable) {
mPickerManager.setEyePickEnable(eyePickEnable);
}
@Deprecated
public void setEyePickChangedListener(IEyePickListener listener){
mPickerManager.setEyePickChangedListener(new CompactEyePickAdapter(listener));
}
@Deprecated
public void setTouchPickListener(ITouchPickListener listener){
mPickerManager.setTouchPickListener(new CompactTouchPickAdapter(listener));
}
public void setEyePickChangedListener(IEyePickListener2 listener){
mPickerManager.setEyePickChangedListener(listener);
}
public void setTouchPickListener(ITouchPickListener2 listener){
mPickerManager.setTouchPickListener(listener);
}
public void setPinchScale(float scale){
mTouchHelper.scaleTo(scale);
}
public void setDirectorFilter(IDirectorFilter filter){
mDirectorFilter.setDelegate(filter);
}
public void addPlugin(MDAbsPlugin plugin){
mPluginManager.add(plugin);
}
public void removePlugin(MDAbsPlugin plugin){
mPluginManager.remove(plugin);
}
public void removePlugins(){
mPluginManager.removeAll();
}
public IMDHotspot findHotspotByTag(String tag){
return mPluginManager.findHotspotByTag(tag);
}
public MDAbsView findViewByTag(String tag){
return mPluginManager.findViewByTag(tag);
}
public void onTextureResize(float width, float height){
mTextureSize.set(0,0,width,height);
}
public void onOrientationChanged(Activity activity) {
mInteractiveModeManager.onOrientationChanged(activity);
}
public void onResume(Context context){
mInteractiveModeManager.onResume(context);
if (mScreenWrapper != null){
mScreenWrapper.onResume();
}
}
public void onPause(Context context){
mInteractiveModeManager.onPause(context);
if (mScreenWrapper != null){
mScreenWrapper.onPause();
}
}
public void onDestroy(){
mGLHandler.post(new Runnable() {
@Override
public void run() {
fireDestroy();
}
});
mGLHandler.markAsDestroy();
}
private void fireDestroy(){
Iterator<MDAbsPlugin> iterator = mPluginManager.getPlugins().iterator();
while (iterator.hasNext()){
MDAbsPlugin plugin = iterator.next();
plugin.destroyInGL();
}
MDAbsPlugin mainPlugin = mProjectionModeManager.getMainPlugin();
if (mainPlugin != null){
mainPlugin.destroyInGL();
}
if (mTexture != null){
mTexture.destroy();
mTexture.release();
mTexture = null;
}
}
/**
* handle touch touch to rotate the model
* @deprecated deprecated since 2.0
*
* @param event
* @return true if handled.
*/
public boolean handleTouchEvent(MotionEvent event) {
Log.e(TAG,"please remove the handleTouchEvent in activity!");
return false;
}
public int getInteractiveMode() {
return mInteractiveModeManager.getMode();
}
public int getDisplayMode(){
return mDisplayModeManager.getMode();
}
public int getProjectionMode(){
return mProjectionModeManager.getMode();
}
public void notifyPlayerChanged(){
if (mTexture != null){
mTexture.notifyChanged();
}
}
public interface IOnSurfaceReadyCallback {
void onSurfaceReady(Surface surface);
}
public interface IBitmapProvider {
void onProvideBitmap(MD360BitmapTexture.Callback callback);
}
public interface IImageLoadProvider {
void onProvideBitmap(Uri uri, MD360BitmapTexture.Callback callback);
}
public interface INotSupportCallback{
void onNotSupport(int mode);
}
public interface IGestureListener {
void onClick(MotionEvent e);
}
public interface IDirectorFilter {
/**
* @param input pitch(x-axis, from -90 to 90 in degree)
* */
float onFilterPitch(float input);
/**
* @param input yaw(y-axis, from -180 to 180 in degree)
* */
float onFilterYaw(float input);
/**
* @param input roll(z-axis, from -180 to 180 in degree)
* */
float onFilterRoll(float input);
}
public static class DirectorFilterAdatper implements IDirectorFilter {
@Override
public float onFilterPitch(float input) {
return input;
}
@Override
public float onFilterYaw(float input) {
return input;
}
@Override
public float onFilterRoll(float input) {
return input;
}
}
interface IAdvanceGestureListener {
void onDrag(float distanceX, float distanceY);
void onPinch(float scale);
}
@Deprecated
public interface IEyePickListener {
void onHotspotHit(IMDHotspot hitHotspot, long hitTimestamp);
}
public interface IEyePickListener2 {
void onHotspotHit(MDHitEvent hitEvent);
}
@Deprecated
public interface ITouchPickListener {
void onHotspotHit(IMDHotspot hitHotspot, MDRay ray);
}
public interface ITouchPickListener2 {
void onHotspotHit(MDHitEvent hitEvent);
}
public static Builder with(Activity activity){
return new Builder(activity);
}
/**
*
*/
public static class Builder {
private int displayMode = DISPLAY_MODE_NORMAL;
private int interactiveMode = INTERACTIVE_MODE_MOTION;
private int projectionMode = PROJECTION_MODE_SPHERE;
private Activity activity;
private int contentType = ContentType.DEFAULT;
private MD360Texture texture;
private INotSupportCallback notSupportCallback;
private IGestureListener gestureListener;
private boolean pinchEnabled; // default false.
private boolean eyePickEnabled = true; // default true.
private BarrelDistortionConfig barrelDistortionConfig;
private IEyePickListener2 eyePickChangedListener;
private ITouchPickListener2 touchPickChangedListener;
private MD360DirectorFactory directorFactory;
private int motionDelay = SensorManager.SENSOR_DELAY_GAME;
private SensorEventListener sensorListener;
private MDGLScreenWrapper screenWrapper;
private IMDProjectionFactory projectionFactory;
private MDPinchConfig pinchConfig;
private IDirectorFilter directorFilter;
private Builder(Activity activity) {
this.activity = activity;
}
public Builder displayMode(int displayMode){
this.displayMode = displayMode;
return this;
}
public Builder interactiveMode(int interactiveMode){
this.interactiveMode = interactiveMode;
return this;
}
public Builder projectionMode(int projectionMode){
this.projectionMode = projectionMode;
return this;
}
public Builder ifNotSupport(INotSupportCallback callback){
this.notSupportCallback = callback;
return this;
}
public Builder asVideo(IOnSurfaceReadyCallback callback){
texture = new MD360VideoTexture(callback);
contentType = ContentType.VIDEO;
return this;
}
public Builder asBitmap(IBitmapProvider bitmapProvider){
notNull(bitmapProvider, "bitmap Provider can't be null!");
texture = new MD360BitmapTexture(bitmapProvider);
contentType = ContentType.BITMAP;
return this;
}
/**
* gesture listener, e.g.
* onClick
* @deprecated please use {@link #listenGesture(IGestureListener)}
*
* @param listener listener
* @return builder
*/
@Deprecated
public Builder gesture(IGestureListener listener) {
gestureListener = listener;
return this;
}
/**
* enable or disable the pinch gesture
*
* @param enabled default is false
* @return builder
*/
public Builder pinchEnabled(boolean enabled) {
this.pinchEnabled = enabled;
return this;
}
/**
* enable or disable the eye picking.
*
* @param enabled default is false
* @return builder
*/
public Builder eyePickEnabled(boolean enabled) {
this.eyePickEnabled = enabled;
return this;
}
/**
* gesture listener, e.g.
* onClick
*
* @param listener listener
* @return builder
*/
public Builder listenGesture(IGestureListener listener) {
gestureListener = listener;
return this;
}
/**
* IPickListener listener
*
* @param listener listener
* @return builder
*/
@Deprecated
public Builder listenEyePick(final IEyePickListener listener){
this.eyePickChangedListener = new CompactEyePickAdapter(listener);
return this;
}
/**
* IPickListener listener
*
* @param listener listener
* @return builder
*/
@Deprecated
public Builder listenTouchPick(final ITouchPickListener listener){
this.touchPickChangedListener = new CompactTouchPickAdapter(listener);
return this;
}
/**
* sensor delay in motion mode.
*
* {@link android.hardware.SensorManager#SENSOR_DELAY_FASTEST}
* {@link android.hardware.SensorManager#SENSOR_DELAY_GAME}
* {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL}
* {@link android.hardware.SensorManager#SENSOR_DELAY_UI}
*
* @param motionDelay default is {@link android.hardware.SensorManager#SENSOR_DELAY_GAME}
* @return builder
*/
public Builder motionDelay(int motionDelay){
this.motionDelay = motionDelay;
return this;
}
public Builder sensorCallback(SensorEventListener callback){
this.sensorListener = callback;
return this;
}
public Builder directorFactory(MD360DirectorFactory directorFactory){
this.directorFactory = directorFactory;
return this;
}
public Builder projectionFactory(IMDProjectionFactory projectionFactory){
this.projectionFactory = projectionFactory;
return this;
}
public Builder barrelDistortionConfig(BarrelDistortionConfig config){
this.barrelDistortionConfig = config;
return this;
}
public Builder pinchConfig(MDPinchConfig config){
this.pinchConfig = config;
return this;
}
public Builder directorFilter(IDirectorFilter filter){
this.directorFilter = filter;
return this;
}
/**
* build it!
*
* @param glViewId will find the GLSurfaceView by glViewId in the giving {@link #activity}
* or find the GLTextureView by glViewId
* @return vr lib
*/
public MDVRLibrary build(int glViewId){
View view = activity.findViewById(glViewId);
if (view instanceof GLSurfaceView){
return build((GLSurfaceView) view);
} else if(view instanceof GLTextureView){
return build((GLTextureView) view);
} else {
throw new RuntimeException("Please ensure the glViewId is instance of GLSurfaceView or GLTextureView");
}
}
public MDVRLibrary build(GLSurfaceView glSurfaceView){
return build(MDGLScreenWrapper.wrap(glSurfaceView));
}
public MDVRLibrary build(GLTextureView glTextureView){
return build(MDGLScreenWrapper.wrap(glTextureView));
}
private MDVRLibrary build(MDGLScreenWrapper screenWrapper){
notNull(texture,"You must call video/bitmap function before build");
if (this.directorFactory == null) directorFactory = new MD360DirectorFactory.DefaultImpl();
if (this.barrelDistortionConfig == null) barrelDistortionConfig = new BarrelDistortionConfig();
if (this.pinchConfig == null) pinchConfig = new MDPinchConfig();
this.screenWrapper = screenWrapper;
return new MDVRLibrary(this);
}
}
public interface ContentType {
int VIDEO = 0;
int BITMAP = 1;
int FBO = 2;
int DEFAULT = VIDEO;
}
}