/*********************************************************************** * mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * 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 org.mt4j.components; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.mt4j.util.math.Vector3D; /** * Acts as a visitor to the scene and collects the pick information if * any objects were hit (picked). * Later, whe can retrieve the nearest picked object and its intersection point. * * @author Christopher Ruff */ public class PickResult { /** The pick list. */ // private ArrayList<MTComponent> pickList; /** The comp to inter section point. */ // private WeakHashMap<MTComponent, Vector3D[]> compToInterSectionPoint; private List<PickEntry> pickEntries; private boolean isAlreadySorted; /** * Sometimes the wrong obj gets picked if they are on the same plane but with different inverted rays.. * probably math rounding off errors with floats etc. (at inverting the ray?) * <br>This makes sure, objs which are checked later for a hit, * (and are probably drawn ontop of the previous ones because drawn later), * are picked more likely. * <br>Still this is kind of a hack */ public static final float HIT_TOLERANCE = 0.1f; //0.03f; //FIXME reset to old value!? /** * Instantiates a new pick result. */ public PickResult() { super(); // pickList = new ArrayList<MTComponent>(); // compToInterSectionPoint = new WeakHashMap<MTComponent, Vector3D[]>(); pickEntries = new ArrayList<PickEntry>(); isAlreadySorted = false; } /** * This should only be called by the scene while this objects visits all scene nodes. * * @param hitObject the hit object * @param intersectionPoint the intersection point * @param distance the distance */ public void addPickedObject(MTComponent hitObject, Vector3D intersectionPoint, float distance){ // pickList.add(hitObject); // compToInterSectionPoint.put(hitObject, new Vector3D[]{intersectionPoint, new Vector3D(distance,distance,distance)}); //hack pickEntries.add(new PickEntry(hitObject, intersectionPoint, distance)); int lastIndex = pickEntries.size()-1; pickEntries.get(lastIndex).originalOrderIndex = lastIndex; isAlreadySorted = false; } /** * Returns the picked component. * This is the last in the list of picked components, not neccessarily the one with * the shortest distance from the pickray (<code>setComposite</code> can interfere with that). But usually it should be * the nearest one to the origin of the pick. :) * * @return the nearest pick result * * the picked component or null if nothing could be picked */ public MTComponent getNearestPickResult(){ if (this.isEmpty()) return null; else{ return this.getPickList().get(0).hitObj; // return this.pickEntries.get(pickEntries.size()-1).hitObj; } /* if (this.isEmpty()) return null; else return pickList.get(pickList.size()-1); */ } public PickEntry getNearestPickEntry(){ if (this.isEmpty()) return null; else{ return this.getPickList().get(0); // return this.pickEntries.get(pickEntries.size()-1).hitObj; } } /** * Gets the pick list. * * @return the pick list */ public List<PickEntry> getPickList() { // return pickList; this.sort(); return pickEntries; } // public void addPickedObjects(ArrayList<MTBaseComponent> pickList) { // pickList.addAll(pickList); // } /** * Returns the distance of the origin of the pick to the nearest picked obj. * * @return the distance nearest pick obj */ public float getDistanceNearestPickObj(){ // if (this.isEmpty()){ // return Float.MAX_VALUE; // }else{ // return getDistanceOfPickedObj(this.getNearestPickResult()); // } return getNearestPickEntry().cameraDistance; } /** * Returns the distance of the origin of the pick to the specified picked obj. * * @param pickedObj the picked obj * * @return the distance of picked obj */ public float getDistanceOfPickedObj(MTComponent pickedObj){ // return compToInterSectionPoint.get(pickedObj)[1].x; for (int i = 0; i < getPickList().size(); i++) { PickEntry p = pickEntries.get(i); if (p.equals(pickedObj)) return p.cameraDistance; } return Float.MAX_VALUE; } /** * Returns the interseciton point of the specified picked obj. * Returns null if the object isnt in the pick list! * * @param pickedObj the picked obj * * @return the inter section point of picked obj */ public Vector3D getInterSectionPointOfPickedObj(MTComponent pickedObj){ // return compToInterSectionPoint.get(pickedObj)[0]; for (int i = 0; i < getPickList().size(); i++) { PickEntry p = pickEntries.get(i); if (p.hitObj.equals(pickedObj)) return p.intersectionPoint; } return null; } /** * Gets the inter section point nearest picked obj. * * @return the inter section point nearest picked obj */ public Vector3D getInterSectionPointNearestPickedObj(){ if (this.isEmpty()){ return null; }else{ return getInterSectionPointOfPickedObj(this.getNearestPickResult()); } } /** * Checks if is empty. * * @return true, if is empty */ public boolean isEmpty(){ // return pickList.isEmpty(); return pickEntries.isEmpty(); } public void sort() { if (!isAlreadySorted){ Collections.sort(pickEntries); isAlreadySorted = true; // printList(); } } public void printList() { sort(); System.out.println("Pick Entries:"); for (int i = 0; i < pickEntries.size(); i++) { PickEntry p = pickEntries.get(i); System.out.println("Entry: " + p.hitObj + " Distance: " + p.cameraDistance + " Intersection: " + p.intersectionPoint); } } public class PickEntry implements Comparable<PickEntry>{ public int originalOrderIndex; public Vector3D intersectionPoint; public float cameraDistance; public MTComponent hitObj; public PickEntry(MTComponent hitObject, Vector3D intersectionPoint2, float distance) { this.hitObj = hitObject; this.intersectionPoint = intersectionPoint2; this.cameraDistance = distance; } //We give the later picked objects with the same distance priority //(by substracting the hit tolerance from their distance) //We do this because they are probably drawn ontop because they are located later in the scene graph //Also, we priorize objects that are drawn with depth buffer disabled because they are also in front of others, //even if camera distance is farther public int compareTo(PickEntry o2) { if (o2.equals(this)){ return 0; } if (this.originalOrderIndex >= o2.originalOrderIndex){ if (this.cameraDistance - HIT_TOLERANCE <= o2.cameraDistance || isDrawnWithoutDepthBuffer(this.hitObj)){ return -1; }else{ return 1; } }else{ if (o2.cameraDistance - HIT_TOLERANCE <= this.cameraDistance || isDrawnWithoutDepthBuffer(o2.hitObj)){ return 1; }else{ return -1; } } } // public boolean addedAfter(PickEntry other){ // return this.originalOrderIndex >= other.originalOrderIndex; // } /** * Checks if is drawn without depth buffer. * Since this is inherited to children we have to check * the parents. * * @param comp the comp * * @return true, if is drawn without depth buffer */ public boolean isDrawnWithoutDepthBuffer(MTComponent comp){ if (comp.isDepthBufferDisabled()) return true; MTComponent p = comp.getParent(); while (p != null){ if (p.isDepthBufferDisabled()) return true; p = p.getParent(); } return false; } } }