| /******************************************************************************* |
| * Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Pierre Allard, |
| * Regent L'Archeveque, |
| <<<<<<< HEAD |
| * Sebastien Gemme |
| * |
| ======= |
| * Sebastien Gemme - initial API and implementation |
| * |
| >>>>>>> refs/heads/eclipse_pa |
| * SPDX-License-Identifier: EPL-1.0 |
| * |
| *******************************************************************************/ |
| package org.eclipse.apogy.common.topology.ui.jme3.internal; |
| |
| import java.util.Iterator; |
| import java.util.concurrent.Callable; |
| |
| import javax.vecmath.Matrix4d; |
| import javax.vecmath.Point3d; |
| |
| import org.eclipse.apogy.common.topology.ApogyCommonTopologyFacade; |
| import org.eclipse.apogy.common.topology.Node; |
| import org.eclipse.apogy.common.topology.ui.ApogyCommonTopologyUIFacade; |
| import org.eclipse.apogy.common.topology.ui.GraphicsContext; |
| import org.eclipse.apogy.common.topology.ui.NodeSelection; |
| import org.eclipse.apogy.common.topology.ui.TopologyPresentationSet; |
| import org.eclipse.apogy.common.topology.ui.jme3.JME3Application; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.swt.widgets.Display; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.jme3.collision.CollisionResult; |
| import com.jme3.collision.CollisionResults; |
| import com.jme3.font.BitmapFont; |
| import com.jme3.font.BitmapText; |
| import com.jme3.input.InputManager; |
| import com.jme3.input.KeyInput; |
| import com.jme3.input.MouseInput; |
| import com.jme3.input.controls.ActionListener; |
| import com.jme3.input.controls.KeyTrigger; |
| import com.jme3.input.controls.MouseButtonTrigger; |
| import com.jme3.math.ColorRGBA; |
| import com.jme3.math.Ray; |
| import com.jme3.math.Vector2f; |
| import com.jme3.math.Vector3f; |
| import com.jme3.renderer.Camera; |
| import com.jme3.renderer.queue.RenderQueue.Bucket; |
| import com.jme3.scene.Geometry; |
| import com.jme3.system.AppSettings; |
| |
| public abstract class MousePickListener implements ActionListener { |
| private static final Logger Logger = LoggerFactory.getLogger(MousePickListener.class); |
| |
| public static final String PICK_ENABLED_EVENT = "KeyboardP"; |
| public static final String PICK_EVENT = "LeftMouseButtonPickEvent"; |
| |
| private final String[] inputs = new String[] { PICK_ENABLED_EVENT, PICK_EVENT }; |
| |
| private Camera camera = null; |
| private final InputManager inputManager; |
| private final JME3Application jme3Application; |
| |
| private BitmapText hudText; |
| private BitmapFont guiFont; |
| |
| private boolean keyBoardInputRegistered = false; |
| private boolean mouseInputRegistered = false; |
| |
| private boolean pickEnabled = false; |
| private boolean busy = false; |
| |
| public MousePickListener(Camera camera, JME3Application jme3Application, InputManager inputManager) { |
| this.camera = camera; |
| this.jme3Application = jme3Application; |
| this.inputManager = inputManager; |
| |
| registerWithInput(); |
| } |
| |
| /** |
| * Method called when a selection is made. Should be overloaded. |
| * |
| * @param event The SelectionChangedEvent. |
| */ |
| protected abstract void fireSelectionChanged(final SelectionChangedEvent event); |
| |
| protected abstract void pickingEnabled(boolean enable); |
| |
| @Override |
| public void onAction(String name, boolean keyPressed, float tpf) { |
| if (name.equals(PICK_EVENT) && keyPressed) { |
| if (this.busy) { |
| Logger.info("Busy !"); |
| } else if (this.pickEnabled) { |
| try { |
| this.busy = true; |
| CollisionResults results = new CollisionResults(); |
| |
| // Convert screen click to 3d position |
| Vector2f click2d = this.inputManager.getCursorPosition(); |
| Vector3f click3d = this.camera.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone(); |
| Vector3f dir = this.camera.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f) |
| .subtractLocal(click3d).normalizeLocal(); |
| |
| // Aim the ray from the clicked spot forwards. |
| Ray ray = new Ray(click3d, dir); |
| |
| // Collect intersections between ray and all nodes in |
| // results list. |
| this.jme3Application.getRootNode().collideWith(ray, results); |
| |
| if (results.size() > 0) { |
| CollisionResult collisionResult = null; |
| Vector3f contactNormal = null; |
| Geometry geometry = null; |
| Node node = null; |
| |
| Iterator<CollisionResult> it = results.iterator(); |
| while (node == null && it.hasNext()) { |
| collisionResult = it.next(); |
| contactNormal = collisionResult.getContactNormal(); |
| geometry = collisionResult.getGeometry(); |
| node = this.jme3Application.getJMERenderEngineDelegate().getTopologyNode(geometry); |
| } |
| |
| if (node != null) { |
| // Transform the pick position in the node frame. |
| Matrix4d m = ApogyCommonTopologyFacade.INSTANCE.expressNodeInRootFrame(node); |
| m.invert(); |
| Point3d relativePosition = new Point3d(collisionResult.getContactPoint().x, |
| collisionResult.getContactPoint().y, collisionResult.getContactPoint().z); |
| m.transform(relativePosition); |
| |
| // Converts the normal |
| javax.vecmath.Vector3f contactNormalVector = new javax.vecmath.Vector3f(contactNormal.x, |
| contactNormal.y, contactNormal.z); |
| m.transform(contactNormalVector); |
| |
| TopologyPresentationSet topologyPresentationSet = null; |
| if (this.jme3Application.getJMERenderEngineDelegate().getTopologyViewer() |
| .getInput() instanceof GraphicsContext) { |
| GraphicsContext graphicsContext = (GraphicsContext) this.jme3Application |
| .getJMERenderEngineDelegate().getTopologyViewer().getInput(); |
| topologyPresentationSet = graphicsContext.getTopologyPresentationSet(); |
| } |
| |
| final NodeSelection nodeSelection = ApogyCommonTopologyUIFacade.INSTANCE |
| .createNodeSelection(topologyPresentationSet, node, relativePosition, |
| contactNormalVector); |
| |
| Logger.debug("--------------------------------------------------------------"); |
| Logger.debug(" PICK SELECTION"); |
| Logger.debug(nodeSelection.toString()); |
| Logger.debug("--------------------------------------------------------------"); |
| Logger.debug("--------------------------------------------------------------"); |
| Logger.debug(" PICK SELECTION"); |
| Logger.debug("Selected Node : " + nodeSelection.getSelectedNode()); |
| Logger.debug("Relative Position : " + nodeSelection.getRelativeIntersectionPoint()); |
| Logger.debug("Absolute Position : " + nodeSelection.getAbsoluteIntersectionPoint()); |
| Logger.debug("Relative Normal : " + nodeSelection.getRelativeIntersectionNormal()); |
| Logger.debug("Absolute Normal : " + nodeSelection.getAbsoluteIntersectionNormal()); |
| Logger.debug("--------------------------------------------------------------"); |
| |
| this.jme3Application.enqueue(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| try { |
| if (MousePickListener.this.jme3Application |
| .getJMERenderEngineDelegate() != null) { |
| StructuredSelection selection = new StructuredSelection(nodeSelection); |
| |
| SelectionChangedEvent event = new SelectionChangedEvent( |
| MousePickListener.this.jme3Application.getJMERenderEngineDelegate() |
| .getTopologyViewer(), |
| selection); |
| |
| Display.getDefault().asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| try { |
| fireSelectionChanged(event); |
| } catch (Exception e) { |
| Logger.error(e.getMessage(), e); |
| } |
| MousePickListener.this.busy = false; |
| } |
| }); |
| } |
| } catch (Throwable t) { |
| } |
| |
| return null; |
| } |
| }); |
| |
| // Runnable runnable = new Runnable() { |
| // @Override |
| // public void run() { |
| // try { |
| // if (jme3Application.getJMERenderEngineDelegate() != null) { |
| // StructuredSelection selection = new StructuredSelection(nodeSelection); |
| // |
| // SelectionChangedEvent event = new SelectionChangedEvent( |
| // jme3Application.getJMERenderEngineDelegate().getTopologyViewer(), |
| // selection); |
| // |
| // fireSelectionChanged(event); |
| // } |
| // } catch (Throwable t) { |
| // |
| // } |
| // |
| // busy = false; |
| // } |
| // }; |
| // |
| // Display.getDefault().asyncExec(runnable); |
| } else { |
| this.busy = false; |
| } |
| |
| } else { |
| this.busy = false; |
| } |
| } catch (Exception e) { |
| this.busy = false; |
| } |
| } |
| } else if (name.equals(PICK_ENABLED_EVENT)) { |
| // Toggles pick enabled. |
| setPickEnabled(!this.pickEnabled); |
| } |
| } |
| |
| public void setEnabled(boolean enabled) { |
| if (enabled) { |
| if (!this.keyBoardInputRegistered) { |
| registerWithInput(); |
| } |
| Logger.info("Pick is enabled."); |
| } else { |
| if (this.keyBoardInputRegistered) { |
| unregisterInput(); |
| } |
| Logger.info("Pick is disabled."); |
| } |
| } |
| |
| public void setPickEnabled(boolean enabled) { |
| pickingEnabled(enabled); |
| |
| if (enabled) { |
| if (!this.mouseInputRegistered) { |
| registerMouseInput(); |
| } |
| } else { |
| if (this.mouseInputRegistered) { |
| unregisterMouseInput(); |
| } |
| } |
| |
| // Updates the GUI. |
| this.jme3Application.enqueue(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| if (enabled) { |
| if (!MousePickListener.this.jme3Application.getGuiNode().getChildren().contains(getHudText())) { |
| updateHudText(); |
| MousePickListener.this.jme3Application.getGuiNode().attachChild(getHudText()); |
| } |
| } else { |
| if (MousePickListener.this.jme3Application.getGuiNode().getChildren().contains(getHudText())) { |
| MousePickListener.this.jme3Application.getGuiNode().detachChild(getHudText()); |
| } |
| } |
| return null; |
| } |
| }); |
| |
| this.pickEnabled = enabled; |
| } |
| |
| protected BitmapText getHudText() { |
| if (this.hudText == null) { |
| this.hudText = new BitmapText(getGuiFont(), false); |
| this.hudText.setSize(this.guiFont.getCharSet().getRenderedSize()); // font |
| // size |
| this.hudText.setColor(ColorRGBA.Yellow); // font color |
| this.hudText.setText("Picking Enabled"); |
| updateHudText(); |
| } |
| |
| return this.hudText; |
| } |
| |
| protected void updateHudText() { |
| BitmapText text = getHudText(); |
| AppSettings settings = this.jme3Application.getAppSettings(); |
| |
| float x = 0; |
| float y = this.hudText.getLineHeight(); |
| |
| Logger.debug(settings.getWidth() + " x " + settings.getHeight()); |
| |
| text.setLocalTranslation(x, y, 0); // position |
| text.setQueueBucket(Bucket.Gui); |
| } |
| |
| protected BitmapFont getGuiFont() { |
| if (this.guiFont == null) { |
| this.guiFont = this.jme3Application.getAssetManager().loadFont("Interface/Fonts/Default.fnt"); |
| } |
| return this.guiFont; |
| } |
| |
| protected void registerMouseInput() { |
| if (!this.inputManager.hasMapping(PICK_EVENT)) { |
| this.inputManager.addMapping(PICK_EVENT, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); |
| this.inputManager.addListener(this, new String[] { PICK_EVENT }); |
| } |
| this.mouseInputRegistered = true; |
| } |
| |
| protected void unregisterMouseInput() { |
| if (this.inputManager.hasMapping(PICK_EVENT)) { |
| this.inputManager.deleteMapping(PICK_EVENT); |
| } |
| this.mouseInputRegistered = false; |
| } |
| |
| /** |
| * Registers inputs with the input manager. |
| * |
| * @param inputManager |
| */ |
| private void registerWithInput() { |
| if (!this.inputManager.hasMapping(PICK_ENABLED_EVENT)) { |
| this.inputManager.addMapping(PICK_ENABLED_EVENT, new KeyTrigger(KeyInput.KEY_P)); |
| this.inputManager.addListener(this, new String[] { PICK_ENABLED_EVENT }); |
| } |
| |
| this.keyBoardInputRegistered = true; |
| } |
| |
| private void unregisterInput() { |
| for (int i = 0; i < this.inputs.length; i++) { |
| if (!this.inputManager.hasMapping(this.inputs[i])) { |
| this.inputManager.deleteMapping(this.inputs[i]); |
| } |
| } |
| |
| this.keyBoardInputRegistered = false; |
| } |
| } |