Package org.jdesktop.wonderland.client.input

Source Code of org.jdesktop.wonderland.client.input.InputPicker$MyCameraListener

/**
* Open Wonderland
*
* Copyright (c) 2010, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.client.input;

import java.awt.Canvas;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.logging.Logger;
import org.jdesktop.mtgame.Entity;
import org.jdesktop.mtgame.CollisionManager;
import org.jdesktop.mtgame.JMECollisionSystem;
import org.jdesktop.mtgame.PickInfo;
import org.jdesktop.mtgame.PickDetails;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.math.Matrix4f;
import com.jme.renderer.Camera;
import com.jme.renderer.AbstractCamera;
import java.awt.Button;
import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.util.EventObject;
import java.util.Iterator;
import java.util.LinkedList;
import org.jdesktop.mtgame.CameraComponent;
import org.jdesktop.mtgame.CollisionComponent;
import org.jdesktop.mtgame.EntityComponent;
import org.jdesktop.wonderland.client.jme.ClientContextJME;
import org.jdesktop.wonderland.client.jme.input.KeyEvent3D;
import org.jdesktop.wonderland.client.jme.input.MouseEvent3D;
import org.jdesktop.wonderland.common.InternalAPI;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import org.jdesktop.wonderland.client.jme.ViewManager;
import org.jdesktop.wonderland.client.jme.input.MouseDraggedEvent3D;
import org.jdesktop.wonderland.client.jme.input.MouseEnterExitEvent3D;
import org.jdesktop.wonderland.common.cell.CellTransform;
import org.jdesktop.wonderland.client.input.InputManager.WindowSwingEventConsumer;
import org.jdesktop.wonderland.client.input.InputManager.WindowSwingEventConsumer.EventAction;
import org.jdesktop.wonderland.client.jme.input.DropTargetDragEnterEvent3D;
import org.jdesktop.wonderland.client.jme.input.DropTargetDragExitEvent3D;

/**
* The abstract base class for an InputPicker singleton. The InputPicker is the part of the
* input subsystem which determines the pick details and the entity which an input event "hits." There are two
* mouse event processing methods. One method is used from WindowSwing code when a WindowSwing exists.
* This method performs a pick, determines whether it hits a WindowSwing and if so returns the pickInfo to the
* WindowSwing code. If a WindowSwing is not hit the pickInfo is stored for later use.
*
* The other mouse event processing method is used for events which don't hit a WindowSwing. These events
* come from the Canvas (via the InputManager). The processing for these events, called "3D events," attempts
* to avoid performing a pick by using pickInfo provided by WindowSwing, if available. (See previous paragraph).
*
* Note: the pick operation happens on the AWT event dispatch thread in all cases.
*
* @author deronj
*/

// TODO: if possible separate out the JME-dependent code into jme.input.InputPicker3D

@InternalAPI
public abstract class InputPicker {

    protected static final Logger logger = Logger.getLogger(InputPicker.class.getName());

    // Bit masks used during the grab calculation
    private static final int PG_SHIFT_DOWN_MASK     = 1<<0;
    private static final int PG_CTRL_DOWN_MASK      = 1<<1;
    private static final int PG_META_DOWN_MASK      = 1<<2;
    private static final int PG_ALT_DOWN_MASK       = 1<<3;
    private static final int PG_ALT_GRAPH_DOWN_MASK = 1<<4;
    private static final int PG_ALL_BUTTONS_MASK =
        PG_SHIFT_DOWN_MASK |
        PG_CTRL_DOWN_MASK  |
        PG_META_DOWN_MASK  |
        PG_ALT_DOWN_MASK   |
        PG_ALT_GRAPH_DOWN_MASK;

    /** The type of grab transition */
    private enum GrabChangeType { GRAB_ACTIVATE, GRAB_DEACTIVATE, GRAB_NO_CHANGE };

    /** Whether a grab is currently active */
    private boolean grabIsActive = false;

    /** The JME collision system (used for picking) */
    // TODO: how do we also handle the other types of collision systems?
    JMECollisionSystem collisionSys;

    /** The destination pick info that the picker computes */
    private PickInfo destPickInfo;

    /** The destination pick info for the previous picked event */
    private PickInfo destPickInfoPrev;

    /** The most-recently picked drop target */
    private PickInfo dropPickInfoPrev;

    // 3 pixels
    private static final int BUTTON_CLICK_POSITION_THRESHOLD = 3;

    // Coordinate of last button press event
    private int buttonLastX, buttonLastY;

    // The pick info of the last time a button was pressed.
    private PickInfo lastButtonPressedPickInfo;
   
    // The pick info of the mouse button press event which started a grab
    private PickInfo grabPickInfo;
   
    /** The camera component to use for picking. */
    private CameraComponent cameraComp;

    /** The canvas to use for picking. */
    private Canvas canvas;

    /** The event distributor associated with this picker */
    protected EventDistributor eventDistributor;

    /** A temporary used during picking */
    private Vector2f eventPointScreen = new Vector2f();

    /** Another temporary used during picking */
    private Vector3f eventPointWorld = new Vector3f();

    /** Another temporary used during picking */
    private Vector3f directionWorld = new Vector3f();

    /** Another temporary used during picking */
    private Ray pickRay = new Ray();

    /**
     * The format of information we pass from the Swing picker to the 3D picker in the case
     * that the event missed a swing window.
     *
     * NOTE: it is tempting to also pass the destination pick info (accounting for grabs) in
     * this queue. But it significantly complicates the reader side of the queue. We can easily
     * recalculate the destination pick info on the reader side.
     */
    private static class PickInfoQueueEntry {
  private PickInfo hitPickInfo;
  private MouseEvent mouseEvent;
  private PickInfoQueueEntry (PickInfo hitPickInfo, MouseEvent mouseEvent) {
      this.hitPickInfo = hitPickInfo;
      // TODO: For verification only. Evetually remove.
      this.mouseEvent = mouseEvent;
  }
    }

    /**
     * When a WindowSwing exists events go to WindowSwingEmbeddedPeer.createCoordinateHandler before
     * for picking before coming to the InputPicker. createCoordinateHandler stores the pick infos
     * for non-WindowSwing events in this queue so we don't need to repick in the picker.
     */
    private LinkedBlockingQueue<PickInfoQueueEntry> swingPickInfos =
  new LinkedBlockingQueue<PickInfoQueueEntry>();


    /**
     * As we examine the entities along the pick ray we need to keep
     * track of the entities which are visible (that is not obscured
     * by an entity whose listeners doesn't propagate to under). We also
     * need to keep track the pick details which will be ultimately sent
     * in the enter/exit events.
     */
    private static class EntityAndPickDetails {
  private Entity entity;
  private PickDetails pickDetails;
  private EntityAndPickDetails (Entity entity, PickDetails pickDetails) {
      this.entity = entity;
      this.pickDetails = pickDetails;
  }
    }

    /** The entities that the pointer is inside for the current event. */
    LinkedList<EntityAndPickDetails> insideEntities = new LinkedList<EntityAndPickDetails>();

    /** The entities that the pointer was inside for the last event. */
    LinkedList<EntityAndPickDetails> insideEntitiesPrev = new LinkedList<EntityAndPickDetails>();

    /** The entities that no longer have the pointer inside. */
    LinkedList<EntityAndPickDetails> noLongerInsideEntities = new LinkedList<EntityAndPickDetails>();

    /** The entities that newly have the pointer inside. */
    LinkedList<EntityAndPickDetails> newlyInsideEntities = new LinkedList<EntityAndPickDetails>();

    /** A dummy AWT component used in enter/exit events. */
    private static Button dummyButton = new Button();

    /** Whether the previous pointer position was inside the given window swing entity. */
    private Entity insideSwingEntityPrev;

    /** The camera movement listener (used by drag events). */
    private MyCameraListener cameraListener = new MyCameraListener();

    /** An object used to lock the following camera-related variables */
    private final Integer cameraLock = new Integer(0);

    /** The current camera position (in world coordinates). */
    private Vector3f cameraPositionWorld = new Vector3f();

    /** The current model view matrix of the camera. */
    private Matrix4f cameraModelViewMatrix;

    /** The current inverse of the model view matrix of the camera. */
    private Matrix4f cameraModelViewMatrixInverse;

    /**
     * Create a new instance of InputPicker.
     */
    protected InputPicker () {
  CollisionManager cm = ClientContextJME.getWorldManager().getCollisionManager();
  collisionSys = (JMECollisionSystem) cm.loadCollisionSystem(JMECollisionSystem.class);
    }

    /**
     * Specify the associated event distributor.
     * @param eventDistributor
     */
    public void setEventDistributor (EventDistributor eventDistributor) {
  this.eventDistributor = eventDistributor;
    }

    /**
     * Picker for mouse events for the Embedded Swing case.
     * To be called by Embedded Swing toolkit createCoordinateHandler.
     *
     * Returns non-null if window is a WindowSwing. If it is a WindowSwing then
     * return the appropriate hit entity and the corresponding pick info.
     * Otherwise save the pickinfos the event to the event distributor as a 3D event.
     *
     * @param awtEvent The event whose entity and pickInfo need to be picked.
     * @return An object of class PickEventReturn, which contains the return
     * values entity and pickDetails.
     */
    public InputManager.PickEventReturn pickMouseEventSwing (MouseEvent awtMouseEvent) {

  logger.info("Picker Swing: received awt event = " + awtMouseEvent);

  // Determine the destination pick info by performing a pick, considering grabs. etc.
  PickInfo hitPickInfo;
  DetermineDestPickInfoReturn ret = determineDestPickInfo(awtMouseEvent);
  if (ret == null) {
      destPickInfo = null;
      hitPickInfo = null;
  } else {
      destPickInfo = ret.destPickInfo;
      hitPickInfo = ret.hitPickInfo;
  }
  logger.fine("destPickInfo = " + destPickInfo);
   logger.fine("hitPickInfo = " + hitPickInfo);

  // Generate 3D enter/exit events associated with this mouse event
  int eventID = awtMouseEvent.getID();
  if (eventID == MouseEvent.MOUSE_MOVED ||
      eventID == MouseEvent.MOUSE_DRAGGED ||
      eventID == MouseEvent.MOUSE_ENTERED ||
      eventID == MouseEvent.MOUSE_EXITED) {
      generateEnterExitEvents(awtMouseEvent, destPickInfo);
  }

  // Check for pick miss
  if (destPickInfo == null || destPickInfo.size() <= 0) {
      // Pick miss. Send it to the event distributor without pick info.
      logger.finest("Picker: pick miss");
      logger.finest("Enqueue null pick info for 3D event");
      logger.finest("awtMouseEvent = " + awtMouseEvent);
      swingPickInfos.add(new PickInfoQueueEntry(null, awtMouseEvent));
      return null;
  }

  // Create the Wonderland event which corresponds to this AWT event
  // (and, for drag events, attach the raw hit pick info).
  MouseEvent3D event = (MouseEvent3D) createWonderlandEvent(awtMouseEvent);

  // Get the destination entity and move pick details into the event
  PickDetails pickDetails = destPickInfo.get(0);
        Entity entity = pickDetails.getEntity();
  logger.fine("Picker: pickDetails = " + pickDetails);
        logger.fine("Picker: entity = " + entity);
        event.setPickDetails(pickDetails);
        if (eventID == MouseEvent.MOUSE_DRAGGED && hitPickInfo != null) {
            MouseDraggedEvent3D de3d = (MouseDraggedEvent3D) event;
            if (hitPickInfo.size() > 0) {
                de3d.setHitPickDetails(hitPickInfo.get(0));
            }
        }

        if (isWindowSwingEntity(entity)) {
            logger.info("Hit window swing entity = " + entity);

            // Get the WindowSwing of the entity
            WindowSwingEventConsumer eventConsumer =
                (WindowSwingEventConsumer) entity.getComponent(WindowSwingEventConsumer.class);
           
            // Treat change control events as 3D events, regardless of control or focus
            EventAction eventAction =  eventConsumer.consumesEvent(event);
            logger.info("Event action = " + eventAction);

            switch (eventAction) {

            case CONSUME_3D:

                // We haven't hit an input sensitive WindowSwing so post the event to the
                // event distributor as a 3D event
                swingPickInfos.add(new PickInfoQueueEntry(hitPickInfo, awtMouseEvent));
                return null;

            case CONSUME_2D:

                // HACK: see doc for this method
                cleanupGrab(awtMouseEvent);

                // Return the event to Swing
                if (eventID == MouseEvent.MOUSE_DRAGGED && hitPickInfo != null &&
                    hitPickInfo.size() > 0) {
                    return new InputManager.PickEventReturn(entity, pickDetails, hitPickInfo.get(0));
                } else {
                    return new InputManager.PickEventReturn(entity, pickDetails, null);
                }

            case DISCARD:
            default:
                return null;
            }
        } else {

            // This is a 3D event
            if (awtMouseEvent.getID() == MouseEvent.MOUSE_RELEASED) {
                // TODO: someday: HACK: workaround for 600
                swingPickInfos.add(new PickInfoQueueEntry(destPickInfo, awtMouseEvent));
            } else {
                swingPickInfos.add(new PickInfoQueueEntry(hitPickInfo, awtMouseEvent));
            }

            return null;
        }
    }

    /**
     * Returns true if the given entity belongs to a WindowSwing.
     */
    private static boolean isWindowSwingEntity (Entity entity) {
  // We know whether it is a WindowSwing entity by looking for the attached WindowSwingEntityComponent
  EntityComponent comp = entity.getComponent(InputManager.WindowSwingViewMarker.class);
  return comp != null;
    }

    /**
     * Mouse Event picker for non-Swing (3D) events.
     * Finds the first consuming entity and then turns the work over to the event deliverer.
     * This method does not return a result but instead enqueues an entry for the event in
     * the input queue of the event deliverer.
     */
    void pickMouseEvent3D (MouseEvent awtEvent) {
  logger.fine("pickMouseEvent3D: Received awt event = " + awtEvent);
  MouseEvent3D event;

  // Determine the destination pick info by reading from the pickInfo Queue, performing a pick,
  // considering grabs. etc.
  DetermineDestPickInfoReturn ret = determineDestPickInfo(awtEvent);
  if (ret == null) {
      destPickInfo = null;
  } else {
      destPickInfo = ret.destPickInfo;
  }
  logger.fine("destPickInfo = " + destPickInfo);
  if (ret != null) {
      logger.fine("hitPickInfo = " + ret.hitPickInfo);
  }

  // Generate enter/exit events associated with this mouse event
  int eventID = awtEvent.getID();
  if (eventID == MouseEvent.MOUSE_MOVED ||
      eventID == MouseEvent.MOUSE_DRAGGED ||
      eventID == MouseEvent.MOUSE_ENTERED ||
      eventID == MouseEvent.MOUSE_EXITED) {
      generateEnterExitEvents(awtEvent, destPickInfo);
  }

  // Check for pick miss
  if (destPickInfo == null || destPickInfo.size() <= 0) {
      // Pick miss. Send it along without pick info.
      logger.finest("Picker: pick miss");
      event = (MouseEvent3D) createWonderlandEvent(awtEvent);
      eventDistributor.enqueueEvent(event, (PickInfo)null);
      return;
  }
  logger.fine("Picker: pick hit: destPickInfo = " + destPickInfo);

  // Create the Wonderland event which corresponds to this AWT event
  // (and, for drag events, attach the raw hit pick info).
  event = (MouseEvent3D) createWonderlandEvent(awtEvent);

  // Do the rest of the work in the EventDistributor
  if (eventID == MouseEvent.MOUSE_DRAGGED && ret != null) {
      eventDistributor.enqueueDragEvent(event, destPickInfo, ret.hitPickInfo);
  } else {
      eventDistributor.enqueueEvent(event, destPickInfo);
  }
    }

    /**
     * Drop Event picker for non-Swing (3D) events.
     * Finds the first consuming entity and then turns the work over to the event deliverer.
     * This method does not return a result but instead enqueues an entry for the event in
     * the input queue of the event deliverer.
     */
    void pickDropEvent (DropTargetEvent dropEvent) {
  logger.fine("pickDrop: Received awt event = " + dropEvent);

  // If the event has coordinates, determine what we are over by
        // performing a pick.  Otherwise send the event to the last
        // object we picked.
        Point location = null;
        if (dropEvent instanceof DropTargetDropEvent) {
            // we are going to use the result in Wonderland, so accept the
            // drop
            ((DropTargetDropEvent) dropEvent).acceptDrop(DnDConstants.ACTION_MOVE);
            location = ((DropTargetDropEvent) dropEvent).getLocation();
        } else if (dropEvent instanceof DropTargetDragEvent) {
            // we are going to use the result in Wonderland, so accept the
            // drag
            ((DropTargetDragEvent) dropEvent).acceptDrag(DnDConstants.ACTION_MOVE);
            location = ((DropTargetDragEvent) dropEvent).getLocation();
        }

        PickInfo pickInfo = null;
        if (location != null) {
            pickInfo = pickEventScreenPos(location.x, location.y);
        }

        // now that we have the current and previous pick infos, we can
        // generate any necessary enter and exit events
        if (dropPickInfoPrev != null && !pickInfosEqual(dropPickInfoPrev, pickInfo)) {
            // generate an exit event
            DropTargetDragExitEvent3D e =
                    new DropTargetDragExitEvent3D(dropEvent);
            eventDistributor.enqueueEvent(e, dropPickInfoPrev);
        }

        if (pickInfo != null && !pickInfosEqual(pickInfo, dropPickInfoPrev) &&
            dropEvent instanceof DropTargetDragEvent)
        {
            // generate an enter event
            DropTargetDragEnterEvent3D e =
                    new DropTargetDragEnterEvent3D((DropTargetDragEvent) dropEvent);
            eventDistributor.enqueueEvent(e, pickInfo);
        }

        // forward along the actual event
        Event event = createWonderlandEvent(dropEvent);
        if (event != null) {
            eventDistributor.enqueueEvent(event, pickInfo);
        }       

        // remember the last object that was notified
        dropPickInfoPrev = pickInfo;

        // if this was a drop, notify the system that it is complete
        if (dropEvent instanceof DropTargetDropEvent) {
            ((DropTargetDropEvent) dropEvent).dropComplete(true);

            // we don't get a drag exit event after a drop, so make sure
            // to reset the previous pickinfo so we don't generate an extra
            // exit
            dropPickInfoPrev = null;
        }
    }

    /**
     * Determine if two PickInfo objects are equivalent
     * @param p1 the first PickInfo
     * @param p2 the second PickInfo
     * @return true if the pickinfos represent the same set of objects, or
     * false if not
     */
    private boolean pickInfosEqual(PickInfo p1, PickInfo p2) {
        // check for null
        if (p1 == null) {
            return (p2 == null);
        } else if (p2 == null) {
            return (p1 == null);
        }

        // compare size of pick list
        if (p1.size() != p2.size()) {
            return false;
        }

        // next compare entities of PickDetail objects
        for (int i = 0; i < p1.size(); i++) {
            PickDetails pd1 = p1.get(i);
            PickDetails pd2 = p2.get(i);

            if (pd1.getEntity() == null) {
                if (pd2.getEntity() != null) {
                    return false;
                }
            } else if (!pd1.getEntity().equals(pd2.getEntity())) {
                return false;
            }
        }

        // if everything is the same, the objects are equal
        return true;
    }

    /**
     * Process key events. No picking is actually performed. Key events are delivered starting at the
     * entity that has the keyboard focus.
     */
    void pickKeyEvent (KeyEvent awtEvent) {
  logger.fine("Picker: received awt event = " + awtEvent);
  KeyEvent3D keyEvent = (KeyEvent3D) createWonderlandEvent(awtEvent);
  eventDistributor.enqueueEvent(keyEvent);
    }

    /** The type of return value returned by determineDestPickInfo. */
    private class DetermineDestPickInfoReturn {
  // The destination pick info, accounting for grabs.
  private PickInfo destPickInfo;
  // The actual, raw pick info from the pick hit, ignoring grabs.
  private PickInfo hitPickInfo;
  private DetermineDestPickInfoReturn (PickInfo destPickInfo, PickInfo hitPickInfo) {
      this.destPickInfo = destPickInfo;
      this.hitPickInfo = hitPickInfo;
  }
    }

    /**
     * Performs a pick on the scene graph and determine the actual destination pick info
     * taking into account button click threshold and mouse button grabbing.
     * Returns the destination pick info in the global member destPickInfo.
     *
     * @param e The mouse event.
     * @return The destination pick info.
     */
    protected DetermineDestPickInfoReturn determineDestPickInfo (MouseEvent e) {
        boolean deactivateGrab = false;

  /* For debug: uncomment this to breakpoint on only drag events
  if (e.getID() == MouseEvent.MOUSE_DRAGGED || e.getID() == MouseEvent.MOUSE_PRESSED) {
      logger.severe("Event is drag or press");
  }
  */

  // See if the WindowSwing has already determined a pickInfo for this event.
  // TODO: right now, button release events are never sent to createCoordinateHandler so they
  // never have pre-calculated pickInfos. I don't yet know if this is an Embedded Swing bug or
  // whether it is a feature.
  PickInfo swingHitPickInfo = null;
  if (swingPickInfos.peek() != null) {
      try {
    PickInfoQueueEntry entry = swingPickInfos.take();

    // TODO: for now, verify that this is the right pickInfo for this event
    // Only check certain fields. Other fields (such as absolute X and Y) are
    // expected to be different
    if (e.getID() == entry.mouseEvent.getID() &&
        e.getX() == entry.mouseEvent.getX() &&
        e.getY() == entry.mouseEvent.getY()) {
        swingHitPickInfo = entry.hitPickInfo;
    } else {
        logger.finest("Swing pickInfo event doesn't match 3D event. Repicking.");
        logger.finest("3D event = " + e);
        logger.finest("pickInfo event = " + entry.mouseEvent);
    }
      } catch (InterruptedException ex) {}
  }

  // Implement the click threshold. Allow click event to be passed along only
  if (e.getID() == MouseEvent.MOUSE_PRESSED) {
      buttonLastX = e.getX();
      buttonLastY = e.getY();
  } else if (e.getID() == MouseEvent.MOUSE_CLICKED) {
      if (!buttonWithinClickThreshold(e.getX(), e.getY())) {
    // Discard the event by returing a miss
    return null;
      }
  }

  // Handle button clicked events specially. The mouse clicked event
  // comes after the grab has terminated. So we do this in order to
  // force the clicked event to go to the same destination as the
  // pressed event
  if (e.getID() == MouseEvent.MOUSE_CLICKED) {
      return new DetermineDestPickInfoReturn(lastButtonPressedPickInfo, lastButtonPressedPickInfo);
  }

  // First perform the pick (the pick details in the info are ordered
  // from least to greatest eye distance.
        PickInfo hitPickInfo;
  if (swingHitPickInfo == null) {
      hitPickInfo = pickEventScreenPos(e.getX(), e.getY());
      logger.finest("Result of pickEventScreenPos = " + hitPickInfo);
      if (hitPickInfo != null) {
    logger.finest("hitPickInfo.size() = " + hitPickInfo.size());
      }
  } else {
      hitPickInfo = swingHitPickInfo;
  }

  /* For Debug
  int n = hitPickInfo.size();
  System.err.println("n = " + n);
  for (int i = 0; i < n; i++) {
      PickDetails pd = hitPickInfo.get(i);
      System.err.println("pd[" + i + "] = " + pd);
      Entity pickEntity = pd.getEntity();
      System.err.println("entity[" + i + "] = " + pickEntity);
  }
  */

  // Calculate how the grab state should change. If the a grab should activate, activate it.
  GrabChangeType grabChange = GrabChangeType.GRAB_NO_CHANGE;
        int eventID = e.getID();
  if (eventID == MouseEvent.MOUSE_PRESSED ||
      eventID == MouseEvent.MOUSE_RELEASED) {

      grabChange = evaluateButtonGrabStateChange(eventID, e);
      if (grabChange == GrabChangeType.GRAB_ACTIVATE) {
    grabIsActive = true;
    grabPickInfo = hitPickInfo;
    logger.finest("Grab activate, grabPickInfo = " + grabPickInfo);
    if (grabPickInfo != null) {
        logger.finest("grabPickInfo.size() = " + grabPickInfo.size());
        if (grabPickInfo.size() > 0) {
      PickDetails pd = grabPickInfo.get(0);
      logger.finest("Grab pickDetails[0] = " + pd);
      if (pd != null) {
          logPickDetailsEntity(pd);
          CollisionComponent cc = pd.getCollisionComponent();
          logger.finest("cc = " + cc);
          if (cc != null) {
        logger.finest("cc entity = " + cc.getEntity());
          }
      }
        }
    }
      }
  }

  // If a grab is active, the event destination pick info will be the grabbed pick info
  PickInfo destPickInfo;
  logger.finest("grabIsActive = " + grabIsActive);
  if (grabIsActive) {
      destPickInfo = grabPickInfo;
      logger.finest("Grab is active, grabPickInfo = " + grabPickInfo);
  } else {
      destPickInfo = hitPickInfo;
  }
  logger.finest("After grab calc, destPickInfo = " + destPickInfo);

  // It is now safe to disable the grab
  if (grabChange == GrabChangeType.GRAB_DEACTIVATE) {
      grabIsActive = false;
      grabPickInfo = null;
  }

  if (e.getID() == MouseEvent.MOUSE_PRESSED) {
      lastButtonPressedPickInfo = destPickInfo;
  }

  logger.fine("Picked awt event = " + e);
  logPickInfo("destPickInfo = ", destPickInfo);
  logPickInfo("hitPickInfo = ", hitPickInfo);

  return new DetermineDestPickInfoReturn(destPickInfo, hitPickInfo);
    }

    public static void logPickInfo (String str, PickInfo pickInfo) {
  logger.fine(str + pickInfo);
  if (pickInfo == null) return;
  logger.fine("pickInfo size = " + pickInfo.size());
  for (int idx = 0; idx < pickInfo.size(); idx++) {
      PickDetails pickDetails = pickInfo.get(idx);
      if (pickDetails == null) continue;
      logger.fine("pickDetails " + idx + ": ");
      logPickDetails(pickDetails);
  }
    }

    private static void logPickDetails (PickDetails pickDetails) {
  logPickDetailsEntity(pickDetails);
    }

    private static void logPickDetailsEntity (PickDetails pickDetails) {
  Entity entity = pickDetails.getEntity();
  logger.fine("pickDetails Entity = " + entity);
    }

    /**
     * Specify the canvas to be used for picking.
     *
     * @param canvas The AWT canvas to use for picking operations.
     */
    public void setCanvas (Canvas canvas) {
  this.canvas = canvas;
    }

    /**
     * Returns the canvas that is used for picking.
     */
    public Canvas getCanvas () {
  return canvas;
    }

    /**
     * Specify the camera component to be used for picking.
     *
     * @param cameraComp The mtgame camera component to use for picking operations.
     */
    void setCameraComponent (CameraComponent cameraComp) {

        // One time addition of camera movement listener
        if (this.cameraComp == null) {
      ViewManager.getViewManager().addCameraListener(cameraListener);    
  }   

  this.cameraComp = cameraComp;
    }

    /**
     * Returns the camera component that is used for picking.
     * <br>
     * INTERNAL ONLY.
     */
    @InternalAPI
    public CameraComponent getCameraComponent () {
  return cameraComp;
    }

    /**
     * The camera movement listener.
     */
    private class MyCameraListener implements ViewManager.CameraListener {
        public void cameraMoved(CellTransform cameraWorldTransform) {
      synchronized (cameraLock) {
    cameraWorldTransform.getTranslation(cameraPositionWorld);
    if (cameraComp != null) {
        Camera camera = cameraComp.getCamera();
        cameraModelViewMatrix = ((AbstractCamera)camera).getModelViewMatrix();
        cameraModelViewMatrixInverse = null;
    }
      }
  }
    }

    /**
     * Returns the current camera position (in world coordinates).
     * <br>
     * INTERNAL ONLY.
     */
    @InternalAPI
    public Vector3f getCameraPosition (Vector3f ret) {
  synchronized (cameraLock) {
      if (ret == null) {
    return new Vector3f(cameraPositionWorld);
      }
      ret.set(cameraPositionWorld);
      return ret;
  }
    }

    /**
     * Returns the current model view matrix of the camera.
     * <br>
     * INTERNAL ONLY.
     */
    @InternalAPI
    public Matrix4f getCameraModelViewMatrix (Matrix4f ret) {
  synchronized (cameraLock) {
      if (ret == null) {
    return new Matrix4f(cameraModelViewMatrix);
      }
      ret.set(cameraModelViewMatrix);
      return ret;
  }
    }

    /**
     * Returns the current inverse model view matrix of the camera.
     * <br>
     * INTERNAL ONLY.
     */
    @InternalAPI
    public Matrix4f getCameraModelViewMatrixInverse (Matrix4f ret) {
  synchronized (cameraLock) {
      if (cameraModelViewMatrixInverse == null) {
    cameraModelViewMatrixInverse = cameraModelViewMatrix.invert(new Matrix4f());
      }
      if (ret == null) {
    return new Matrix4f(cameraModelViewMatrixInverse);
      }
      ret.set(cameraModelViewMatrixInverse);
      return ret;
  }
    }

    /**
     * Calculates the ray to use for picking, based on the given screen coordinates.
     */
    Ray calcPickRayWorld (int x, int y) {
        Ray result = null;
  // Get the world space coordinates of the eye position
  Camera camera = cameraComp.getCamera();
  Vector3f eyePosWorld = camera.getLocation();

  // Convert the event from AWT coords to JME float screen space.
  // Need to invert y because (0f, 0f) is at the button left corner.
  eventPointScreen.setX((float)x);
  eventPointScreen.setY((float)(canvas.getHeight()-1-y));

  // Get the world space coordinates of the screen space point from the event
  // (The glass plate of the screen is considered to be at at z = 0 in world space
        try { // May fail if jME thinks the camera matrix is singular
            camera.getWorldCoordinates(eventPointScreen, 0f, eventPointWorld);
            // Compute the diff and create the ray
            eventPointWorld.subtract(eyePosWorld, directionWorld);
            result = new Ray(eyePosWorld, directionWorld.normalize());
        } catch (ArithmeticException ex) {
            logger.log(Level.SEVERE,"Problem getting world space coords for pick ray.", ex);
            result = new Ray();
        }
        return result;
    }

    /**
     * Actually perform the pick.
     */
    PickInfo pickEventScreenPos (int x, int y) {
  if (cameraComp == null) return null;

  logger.fine("pick at " + x + ", " + y);
  Ray pickRayWorld = calcPickRayWorld(x, y);

  // Note: pickAll is needed to in order to pick through transparent objects.
  return collisionSys.pickAllWorldRay(pickRayWorld, true, false/*TODO:interp*/,
                                            true, /* for now, always include ortho objects in picking */
                                            cameraComp);
    }

    private GrabChangeType evaluateButtonGrabStateChange (int eventID, MouseEvent e) {
  int modifiers = convertAwtEventModifiersToPassiveGrabModifiers(e.getModifiers());

  if (eventID == MouseEvent.MOUSE_PRESSED) {
      // Button press
      return GrabChangeType.GRAB_ACTIVATE;
  } else if (eventID == MouseEvent.MOUSE_RELEASED &&
             (modifiers & PG_ALL_BUTTONS_MASK) == 0) {
      // Button release: Similar to X11: terminate grab only when all buttons are released 
      return GrabChangeType.GRAB_DEACTIVATE;
  }

  return GrabChangeType.GRAB_NO_CHANGE;
    }

    // Returns true if the button release is close enough to the button press
    // so as to consitute a click event.
    // Note: These are Java-on-Windows behavior. There is no click event position
    // threshold on Java-on-Linux. But Hideya wants the Windows behavior.

    private final boolean buttonWithinClickThreshold (int x, int y) {
  return Math.abs(x - buttonLastX) <= BUTTON_CLICK_POSITION_THRESHOLD &&
         Math.abs(y - buttonLastY) <= BUTTON_CLICK_POSITION_THRESHOLD;
    }

    private int convertAwtEventModifiersToPassiveGrabModifiers (int modifiers) {
  int pgModifiers = 0;

        if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
      pgModifiers |= PG_SHIFT_DOWN_MASK;
  }

        if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) {
      pgModifiers |= PG_CTRL_DOWN_MASK;
  }

  if ((modifiers & InputEvent.META_DOWN_MASK) != 0) {
      pgModifiers |= PG_META_DOWN_MASK;
  }

  if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
      pgModifiers |= PG_ALT_DOWN_MASK;
  }

  if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
      pgModifiers |= PG_ALT_GRAPH_DOWN_MASK;
  }

  return pgModifiers;
    }

    /**
     * Converts a 2D AWT event into a Wonderland event.
     */
    protected abstract Event createWonderlandEvent (EventObject eventObj);

    /**
     * Generate the appropriate enter/exit events.
     *
     * NOTE: the pointer can be inside an entity, but if this entity is obscured by
     * an entity with event listeners that don't propagate to under then the obscured
     * entity is effectively exitted. This is an example of an "event shadow."
     */
    private void generateEnterExitEvents (MouseEvent awtEvent, PickInfo pickInfo) {

        MouseEnterExitEvent3D enterEventProto =
      createEnterExitEventFromAwtEvent(awtEvent, MouseEvent.MOUSE_ENTERED);
  MouseEnterExitEvent3D exitEventProto =
      createEnterExitEventFromAwtEvent(awtEvent, MouseEvent.MOUSE_EXITED);

  // Calculate which entities have the pointer inside
  calcInsideEntities(awtEvent, enterEventProto, pickInfo);

  //System.err.println("awtEvent = " + awtEvent);
  //System.err.println("insideEntities = " + insideEntities);

  // Calculate entities which had the pointer inside on the last event
  // but which no longer have the pointer inside
  noLongerInsideEntities.clear();
  for (EntityAndPickDetails entry : insideEntitiesPrev) {
      boolean found = false;
      for (EntityAndPickDetails entryInside : insideEntities) {
    if (entry.entity.equals(entryInside.entity)) {
        found = true;
        break;
    }
      }
      if (!found) {
    noLongerInsideEntities.add(entry);
      }
  }

  //System.err.println("noLongerInsideEntities = " + noLongerInsideEntities);

  // Calculate entities which did not have the pointer inside on the last
  // event but now have the pointer inside.
  newlyInsideEntities.clear();
  for (EntityAndPickDetails entry : insideEntities) {
      boolean found = false;
      for (EntityAndPickDetails entryInsidePrev : insideEntitiesPrev) {
    if (entry.entity.equals(entryInsidePrev.entity)) {
        found = true;
        break;
    }
      }
      if (!found) {
    newlyInsideEntities.add(entry);
      }
  }

  //System.err.println("newlyInsideEntities = " + newlyInsideEntities);

  // Send the exit events to the no longer inside entities.
  sendExitEvents(exitEventProto, pickInfo);

  // Send the enter events to the newly inside entities.
  sendEnterEvents(enterEventProto, pickInfo);

  // Remember the inside entities for the event
  insideEntitiesPrev.clear();
  insideEntitiesPrev.addAll(insideEntities);
    }


    /**
     * Calculate the current set of entities the pointer is inside.
     */
    private void calcInsideEntities (MouseEvent awtEvent, MouseEnterExitEvent3D enterEventProto,
             PickInfo pickInfo) {
 
  // Calculate the current set of entities the pointer is inside.
  insideEntities.clear();
  if (awtEvent.getID() == MouseEvent.MOUSE_EXITED || pickInfo == null ||
      pickInfo.size() <= 0) {
      // Note: Canvas exit event is treated just like a pick miss (pickInfo is null)
      return;
  }

  // Gather up entities which intersect the pick ray until we encounter an
  // entity which doesn't propagate to under.
  boolean propagatesToUnder = true;
  PickDetails pickDetails = pickInfo.get(0);
  int idx = 0;
  while (pickDetails != null && idx < destPickInfo.size() && propagatesToUnder) {
      Entity entity = pickDetails.getEntity();
      /*
      if (pickDetails != null) {
    CollisionComponent cc = pickDetails.getCollisionComponent();
    logger.finest("pd cc = " + cc);
    if (cc != null) {
        logger.finest("cc entity = " + cc.getEntity());
    }
      }
      */

      if (entity == null) {
    idx++;
    if (idx < destPickInfo.size()) {
        pickDetails = destPickInfo.get(idx);
    } else {
        pickDetails = null;
    }
    continue;
      }

      insideEntities.add(new EntityAndPickDetails(entity, pickDetails));
     
      propagatesToUnder = false;
      EventListenerCollection listeners = (EventListenerCollection)
    entity.getComponent(EventListenerCollection.class);
     
      if (listeners == null) {
    propagatesToUnder = false;
      } else {
    Iterator<EventListener> it = listeners.iterator();
    while (it.hasNext()) {
        EventListener listener = it.next();
        if (listener.isEnabled()) {
      MouseEnterExitEvent3D distribEvent = (MouseEnterExitEvent3D)
          EventDistributor.createEventForEntity(enterEventProto, entity);
      distribEvent.setPickDetails(pickDetails);
      distribEvent.setPickInfo(pickInfo);
      // TODO: someday: decommit for now
      //propagatesToUnder |= listener.propagatesToUnder(distribEvent);
        }
    }
    if (propagatesToUnder) {
        idx++;
        if (idx < destPickInfo.size()) {
      pickDetails = destPickInfo.get(idx);
        } else {
      pickDetails = null;
        }
    }
      }     
  }
    }

    /**
     * Send the exit events to the no longer inside entities.
     */
    private void sendExitEvents (MouseEnterExitEvent3D exitEventProto, PickInfo pickInfo) {
  for (EntityAndPickDetails entry : noLongerInsideEntities) {
      MouseEnterExitEvent3D exitEvent = (MouseEnterExitEvent3D)
    EventDistributor.createEventForEntity(exitEventProto, entry.entity);
      exitEvent.setPickDetails(entry.pickDetails);
      exitEvent.setPickInfo(pickInfo);
      //System.err.println("Try sending exitEvent = " + exitEvent);
      eventDistributor.tryGlobalListeners(exitEvent);
      //System.err.println("Try entity = " + entry.entity);
      tryListenersForEntity(entry.entity, exitEvent);
  }
    }

    /**
     * Send the enter events to the newly inside entities.
     */
    private void sendEnterEvents (MouseEnterExitEvent3D enterEventProto, PickInfo pickInfo) {
  for (EntityAndPickDetails entry : newlyInsideEntities) {
      MouseEnterExitEvent3D enterEvent = (MouseEnterExitEvent3D)
    EventDistributor.createEventForEntity(enterEventProto, entry.entity);
      enterEvent.setPickDetails(entry.pickDetails);
      enterEvent.setPickInfo(pickInfo);
      eventDistributor.tryGlobalListeners(enterEvent);
      tryListenersForEntity(entry.entity, enterEvent);
  }
    }

    /**
     * Try to send the given event to the listeners for the given entity.
     */
    private void tryListenersForEntity (Entity entity, Event event) {
  EventListenerCollection listeners =
      (EventListenerCollection) entity.getComponent(EventListenerCollection.class);
  if (listeners != null && listeners.size() > 0) {
      Iterator<EventListener> it = listeners.iterator();
      while (it.hasNext()) {
    EventListener listener = it.next();
    if (listener.isEnabled()) {
        logger.fine("Calling consume for listener " + listener);
        if (listener.consumesEvent(event)) {
      logger.fine("CONSUMED by entity " + entity);
      listener.postEvent(event);
        }
    }
      }
  }
    }

    private MouseEnterExitEvent3D createEnterExitEventFromAwtEvent (MouseEvent awtEvent, int id) {
  int x = awtEvent.getX();
  int y = awtEvent.getY();
  long when = awtEvent.getWhen();
  int modifiers = awtEvent.getModifiers();
  int button = awtEvent.getButton();
  MouseEvent me = new MouseEvent(dummyButton, id, when, modifiers, x, y, 0, false, button);
  return (MouseEnterExitEvent3D) createWonderlandEvent(me);
    }

    /**
     * HACK: This is called on a WindowSwing hit. If embedded swing is going to activate a grab
     * on this event then we must clean up any grabbing which the picker performed by deactivating
     * the grab. The proper way to handle this situation is to avoid activating the grab in first
     * place but this would greatly complicate the picker code. So we choose to take the more
     * expedient route and just "back out" the grab. The test case is as follows:
     *
     * 1. Perform a mouse press inside a WindowSwing.
     * 2. Move the cursor outside and then hit an avatar movement key.
     *
     * Unless we clean up the grab the key press will do nothing.
     */
    private void cleanupGrab (MouseEvent e) {
  if (grabIsActive && !isMouseGrab(e) && e.getID() != MouseEvent.MOUSE_CLICKED) {
      grabIsActive = false;
      grabPickInfo = null;
  }
    }

    //copied from Scenario EmbeddedSwing EmbeddedEventQueue.isMouseGrab
    /* This method effectively returns whether or not a mouse button was down
     * just BEFORE the event happened.  A better method name might be
     * wasAMouseButtonDownBeforeThisEvent().
     */
    private static boolean isMouseGrab(MouseEvent e) {
        int modifiers = e.getModifiersEx();
       
        if(e.getID() == MouseEvent.MOUSE_PRESSED
                || e.getID() == MouseEvent.MOUSE_RELEASED) {
            switch (e.getButton()) {
            case MouseEvent.BUTTON1:
                modifiers ^= InputEvent.BUTTON1_DOWN_MASK;
                break;
            case MouseEvent.BUTTON2:
                modifiers ^= InputEvent.BUTTON2_DOWN_MASK;
                break;
            case MouseEvent.BUTTON3:
                modifiers ^= InputEvent.BUTTON3_DOWN_MASK;
                break;
            }
        }
        /* modifiers now as just before event */
        return ((modifiers & (InputEvent.BUTTON1_DOWN_MASK
                              | InputEvent.BUTTON2_DOWN_MASK
                              | InputEvent.BUTTON3_DOWN_MASK)) != 0);
    }
}


TOP

Related Classes of org.jdesktop.wonderland.client.input.InputPicker$MyCameraListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.