content: package com.gamatechno.vrlib;
  
  import android.view.MotionEvent;
  
  import com.gamatechno.vrlib.common.MDMainHandler;
  import com.gamatechno.vrlib.common.VRUtil;
  import com.gamatechno.vrlib.model.MDDirectorSnapshot;
  import com.gamatechno.vrlib.model.MDHitEvent;
  import com.gamatechno.vrlib.model.MDHitPoint;
  import com.gamatechno.vrlib.model.MDRay;
  import com.gamatechno.vrlib.plugins.MDAbsPlugin;
  import com.gamatechno.vrlib.plugins.MDPluginAdapter;
  import com.gamatechno.vrlib.plugins.MDPluginManager;
  import com.gamatechno.vrlib.plugins.hotspot.IMDHotspot;
  import com.gamatechno.vrlib.strategy.display.DisplayModeManager;
  import com.gamatechno.vrlib.strategy.projection.ProjectionModeManager;
  
  import java.util.LinkedList;
  import java.util.List;
  
  import static com.gamatechno.vrlib.common.VRUtil.checkGLThread;
  import static com.gamatechno.vrlib.common.VRUtil.checkMainThread;
  
  
  /**
   * Created by hzqiujiadi on 16/8/7.
   * hzqiujiadi ashqalcn@gmail.com
   */
  public class MDPickerManager {
  
      private static final String TAG = "MDPickerManager";
  
      private static final int HIT_FROM_EYE = 1;
  
      private static final int HIT_FROM_TOUCH = 2;
  
      private boolean mEyePickEnable;
  
      private DisplayModeManager mDisplayModeManager;
  
      private ProjectionModeManager mProjectionModeManager;
  
      private MDPluginManager mPluginManager;
  
      private MDVRLibrary.IEyePickListener2 mEyePickChangedListener;
  
      private MDVRLibrary.ITouchPickListener2 mTouchPickListener;
  
      private EyePickPoster mEyePickPoster = new EyePickPoster();
  
      private TouchPickPoster mTouchPickPoster = new TouchPickPoster();
  
      private RayPickAsTouchMainTask mRayPickAsTouchRunnable = new RayPickAsTouchMainTask();
  
      private RayPickAsEyeMainTask mRayPickAsEyeRunnable = new RayPickAsEyeMainTask();
  
      private DirectorContext mDirectorContext = new DirectorContext();
  
      private final Object mDirectorLock = new Object();
  
      private MDVRLibrary.IGestureListener mTouchPicker = new MDVRLibrary.IGestureListener() {
          @Override
          public void onClick(MotionEvent e) {
              mRayPickAsTouchRunnable.setEvent(e.getX(), e.getY());
              mRayPickAsTouchRunnable.run();
          }
      };
  
      private MDAbsPlugin mEyePicker = new MDPluginAdapter() {
  
          private long pickTs;
  
          // gl thread
          @Override
          public void beforeRenderer(int totalWidth, int totalHeight) {
              synchronized (mDirectorLock){
                  mDirectorContext.snapshot(mProjectionModeManager.getDirectors());
              }
  
              if (isEyePickEnable()){
                  long current = System.currentTimeMillis();
                  if (current - pickTs > 100){
                      MDMainHandler.sharedHandler().post(mRayPickAsEyeRunnable);
                      pickTs = current;
                  }
              }
          }
      };
  
      private MDPickerManager(Builder params) {
          this.mDisplayModeManager = params.displayModeManager;
          this.mProjectionModeManager = params.projectionModeManager;
          this.mPluginManager = params.pluginManager;
      }
  
      public boolean isEyePickEnable() {
          return mEyePickEnable;
      }
  
      public void setEyePickEnable(boolean eyePickEnable) {
          this.mEyePickEnable = eyePickEnable;
      }
  
      // main thread.
      private void rayPickAsTouch(float  x, float y, DirectorContext directorContext) {
          int size = mDisplayModeManager.getVisibleSize();
          if (size == 0){
              return;
          }
  
          MDDirectorSnapshot snapshot = directorContext.getSnapshot(0);
          if (snapshot == null){
              return;
          }
  
          int itemWidth = (int) snapshot.getViewportWidth();
  
          int index = (int) (x / itemWidth);
          if (index >= size){
              return;
          }
  
          snapshot = directorContext.getSnapshot(index);
          if (snapshot == null){
              return;
          }
  
          MDRay ray = VRUtil.point2Ray(x - itemWidth * index, y, snapshot);
  
          pick(ray, HIT_FROM_TOUCH);
      }
  
      // main thread.
      private void rayPickAsEye(DirectorContext mDirectorContext) {
          MDDirectorSnapshot snapshot = mDirectorContext.getSnapshot(0);
          if (snapshot == null){
              return;
          }
          MDRay ray = VRUtil.point2Ray(snapshot.getViewportWidth() / 2, snapshot.getViewportHeight() / 2, snapshot);
  
          pick(ray, HIT_FROM_EYE);
      }
  
      private IMDHotspot pick(MDRay ray, int hitType){
          if (ray == null) return null;
          return hitTest(ray, hitType);
      }
  
      private IMDHotspot hitTest(MDRay ray, int hitType) {
          // main thread
          checkMainThread("hitTest must in main thread");
  
          List<MDAbsPlugin> plugins = mPluginManager.getPlugins();
          IMDHotspot hitHotspot = null;
          MDHitPoint currentDistance = MDHitPoint.notHit();
  
          for (MDAbsPlugin plugin : plugins) {
              if (plugin instanceof IMDHotspot) {
                  IMDHotspot hotspot = (IMDHotspot) plugin;
                  MDHitPoint tmpDistance = hotspot.hit(ray);
                  if (!tmpDistance.isNotHit() && tmpDistance.nearThen(currentDistance)){
                      hitHotspot = hotspot;
                      currentDistance = tmpDistance;
                  }
              }
          }
  
          switch (hitType) {
              case HIT_FROM_TOUCH:
                  // only post the hotspot which is hit.
                  if (hitHotspot != null && !currentDistance.isNotHit()){
                      hitHotspot.onTouchHit(ray);
                      mTouchPickPoster.fire(hitHotspot, ray, currentDistance);
                  }
                  break;
  
              case HIT_FROM_EYE:
                  mEyePickPoster.fire(hitHotspot, ray, currentDistance);
                  break;
          }
  
          return hitHotspot;
      }
  
      public MDVRLibrary.IGestureListener getTouchPicker() {
          return mTouchPicker;
      }
  
      public MDAbsPlugin getEyePicker() {
          return mEyePicker;
      }
  
      public static Builder with() {
          return new Builder();
      }
  
      public void setEyePickChangedListener(MDVRLibrary.IEyePickListener2 eyePickChangedListener) {
          this.mEyePickChangedListener = eyePickChangedListener;
      }
  
      public void setTouchPickListener(MDVRLibrary.ITouchPickListener2 touchPickListener) {
          this.mTouchPickListener = touchPickListener;
      }
  
      void resetEyePick(){
          if (mEyePickPoster != null){
              mEyePickPoster.setHit(null);
          }
      }
  
      public static class Builder {
          private DisplayModeManager displayModeManager;
          private ProjectionModeManager projectionModeManager;
          private MDPluginManager pluginManager;
  
          private Builder() {
          }
  
          public MDPickerManager build(){
              return new MDPickerManager(this);
          }
  
          public Builder setPluginManager(MDPluginManager pluginManager) {
              this.pluginManager = pluginManager;
              return this;
          }
  
          public Builder setDisplayModeManager(DisplayModeManager displayModeManager) {
              this.displayModeManager = displayModeManager;
              return this;
          }
  
          public Builder setProjectionModeManager(ProjectionModeManager projectionModeManager) {
              this.projectionModeManager = projectionModeManager;
              return this;
          }
      }
  
      private class RayPickAsTouchMainTask implements Runnable {
          float x;
          float y;
  
          public void setEvent(float x, float y){
              this.x = x;
              this.y = y;
          }
  
          @Override
          public void run() {
              synchronized (mDirectorLock){
                  rayPickAsTouch(x, y, mDirectorContext);
              }
          }
      }
  
      private class RayPickAsEyeMainTask implements Runnable {
  
          @Override
          public void run() {
              synchronized (mDirectorLock){
                  rayPickAsEye(mDirectorContext);
              }
          }
      }
  
      private class EyePickPoster {
  
          private IMDHotspot hit;
  
          private long timestamp;
  
          void fire(IMDHotspot hit, MDRay ray, MDHitPoint hitPoint) {
              setHit(hit);
  
              MDHitEvent event = MDHitEvent.obtain();
              event.setHotspot(hit);
              event.setRay(ray);
              event.setTimestamp(timestamp);
              event.setHitPoint(hitPoint);
  
              if (this.hit != null){
                  this.hit.onEyeHitIn(event);
              }
  
              if (mEyePickChangedListener != null){
                  mEyePickChangedListener.onHotspotHit(event);
              }
  
              MDHitEvent.recycle(event);
          }
  
          void setHit(IMDHotspot hit){
  
              if (this.hit != hit){
                  if (this.hit != null){
                      this.hit.onEyeHitOut(timestamp);
                  }
  
                  timestamp = System.currentTimeMillis();
              }
  
              this.hit = hit;
          }
      }
  
      private class TouchPickPoster {
  
          void fire(IMDHotspot hitHotspot, MDRay ray, MDHitPoint hitPoint) {
  
              if (mTouchPickListener != null){
                  MDHitEvent event = MDHitEvent.obtain();
                  event.setHotspot(hitHotspot);
                  event.setRay(ray);
                  event.setTimestamp(System.currentTimeMillis());
                  event.setHitPoint(hitPoint);
                  mTouchPickListener.onHotspotHit(event);
                  MDHitEvent.recycle(event);
              }
          }
      }
  
      private static class DirectorContext {
  
          private int size;
  
          private List<MDDirectorSnapshot> list = new LinkedList<>();
  
          public void snapshot(List<MD360Director> directorList){
              checkGLThread("snapshot must in gl thread!");
  
              ensureSize(directorList.size());
              for (int i = 0; i < directorList.size(); i++){
                  list.get(i).copy(directorList.get(i));
              }
          }
  
          private void ensureSize(int size){
              this.size = size;
  
              while (list.size() < size){
                  list.add(new MDDirectorSnapshot());
              }
          }
  
          public MDDirectorSnapshot getSnapshot(int i) {
              if (i < size){
                  return list.get(0);
              }
  
              return null;
          }
      }
  }
  