/* * Project Info: http://jcae.sourceforge.net * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * (C) Copyright 2005, by EADS CRC * (C) Copyright 2007, by EADS France */ package org.jcae.viewer3d.cad; import java.awt.Color; import java.util.*; import java.util.logging.Logger; import javax.media.j3d.*; import javax.vecmath.Color3f; import org.jcae.viewer3d.*; import org.jcae.viewer3d.cad.occ.OCCFaceDomain; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; import com.sun.j3d.utils.geometry.Stripifier; /** * A Viewable specilized to display CAD scenes. * The selected elements are highlighted. * @author Jerome Robert * @todo all methods must be implemented. */ public class ViewableCAD extends ViewableAdaptor { private static final float zFactorAbs=Float.parseFloat(System.getProperty( "javax.media.j3d.zFactorAbs", "20.0f")); private static final float zFactorRel=Float.parseFloat(System.getProperty( "javax.media.j3d.zFactorRel", "2.0f")); private final static Color3f BACK_FACE_COLOR=new Color3f(0.5f,1f,0.5f); private final static Color3f FRONT_FACE_COLOR=new Color3f(0.5f,0.5f,1f); private final static Color3f SELECTED_FACE_COLOR=new Color3f(1.0f, 0, 0); public final static PolygonAttributes polygonAttrFront = new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_FRONT, 20.0f * zFactorAbs, false, zFactorRel); public final static PolygonAttributes polygonAttrBack = new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_BACK, 20.0f * zFactorAbs, false, zFactorRel); public final static PolygonAttributes polygonAttrNone = new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 20.0f * zFactorAbs, false, zFactorRel); static { polygonAttrFront.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE); polygonAttrBack.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE); polygonAttrNone.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE); } protected static abstract class CADPickingInfo { private int id; public int getID() { return id; } protected CADPickingInfo(int id) { this.id = id; } } protected static class FacePickingInfo extends CADPickingInfo { Material material1, material2; Color3f oldColor1, oldColor2; public FacePickingInfo(int id) { super(id); } public FacePickingInfo(int id, Material m1, Color3f color) { super(id); material1 = m1; oldColor1 = color; } public FacePickingInfo(int id, Material m1, Material m2, Color3f c1, Color3f c2) { super(id); material1 = m1; material2 = m2; oldColor1 = c1; oldColor2 = c2; } public Material getMaterial1() { return material1; } public Material getMaterial2() { return material2; } public Color3f getColor1() { return oldColor1; } public Color3f getColor2() { return oldColor2; } } protected static class EdgePickingInfo extends CADPickingInfo { Appearance appearance; ColoringAttributes coloringAttributes; /** * * @param id * @param appearance * @param coloringAttributes */ public EdgePickingInfo(int id,Appearance appearance,ColoringAttributes coloringAttributes) { super(id); this.appearance = appearance; this.coloringAttributes = coloringAttributes; } } protected static class VertexPickingInfo extends CADPickingInfo { Appearance appearance; Shape3D shape3D; /** * * @param id * @param appearance * @param coloringAttributes */ public VertexPickingInfo(int id, Appearance appearance, Shape3D shape3D) { super(id); this.appearance = appearance; this.shape3D = shape3D; } } public final static short NONE_SELECTION=0; public final static short DOMAIN_SELECTION=1; public final static short FACE_SELECTION=2; public final static short EDGE_SELECTION=3; public final static short VERTEX_SELECTION=4; public final static short MULTI_SELECTION=5; public final static int SELECTION_INVERT=0; public final static int SELECTION_ADD=1; public final static int SELECTION_REMOVE=2; public final static int SELECTION_INTERSECT=3; private short selectionMode=FACE_SELECTION; private int selectionAction; private CADProvider provider; private BranchGroup branchGroup=new BranchGroup(); private List selectionListeners=new ArrayList(); private Map<Integer, FacePickingInfo> facesInfo; private Map<Integer, EdgePickingInfo> edgesInfo; private Map<Integer, VertexPickingInfo> verticesInfo; private Collection<Integer> selectedFaces=new HashSet<Integer>(); private Collection<Integer> selectedEdges=new HashSet<Integer>(); private Collection<Integer> selectedVertices=new HashSet<Integer>(); private String name; private LineAttributes lineAttributes=new LineAttributes(); private Node edgesNode; private Node facesNode; private Node verticesNode; /** * */ public ViewableCAD(CADProvider provider) { this.provider=provider; branchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); branchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); branchGroup.setCapability(Group.ALLOW_CHILDREN_READ); domainsChanged(provider.getDomainIDs()); } /* (non-Javadoc) * @see jcae.viewer3d.Viewable#domainsChanged(java.util.Collection) */ @Override public void domainsChangedPerform(int[] domainId) { if(domainId==null) domainId=new int[]{0,1,2}; if(branchGroup.numChildren()>0) branchGroup.removeAllChildren(); for(int i=0;i<domainId.length;i++){ switch(domainId[i]){ case 0 : //Edges edgesNode=createEdgesNode((CADDomain) provider.getDomain(0)); branchGroup.addChild(edgesNode); break; case 1 : //Faces facesNode=createFacesNode((CADDomain) provider.getDomain(1)); branchGroup.addChild(facesNode); break; case 2 : //Vertices verticesNode=createVerticesNode((CADDomain) provider.getDomain(2)); branchGroup.addChild(verticesNode); break; } } } /* (non-Javadoc) * @see jcae.viewer3d.Viewable#getDomainProvider() */ @Override public DomainProvider getDomainProvider() { return provider; } /* (non-Javadoc) * @see jcae.viewer3d.Viewable#setDomainVisible(java.util.Map) */ @Override public void setDomainVisible(Map<Integer, Boolean> map) { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } /** * Set the picking mode of this viewable. * Allowed values are: * <ul> * <li>0, for none selection</li> * <li>1, for domain selection (see CADProvider for how CADDomain are created)</li> * <li>2, for face selection</li> * <li>3, for edge selection</li> * <li>4, for vertex selection</li> * <li>5, for multimodal (face, edge, vertex) selection</li> * </ul> * @param mode */ public void setSelectionMode(short mode) { selectionMode=mode; setPickable(verticesNode, mode==VERTEX_SELECTION); setPickable(facesNode, mode==FACE_SELECTION); setPickable(edgesNode, mode==EDGE_SELECTION); } /** Set sub shape3d pickable status */ private void setPickable(Node node, boolean b) { if(node instanceof Group) { Group g=(Group)node; for(int i=0; i<g.numChildren(); i++) { setPickable(g.getChild(i), b); } } else if(node instanceof Shape3D) { node.setPickable(b); } } public void setSelectionMode(short mode,boolean unselectAll){ if(unselectAll) unselectAll(); setSelectionMode(mode); } /** * Add a selectionListener to this viewable * As an optimisation, when there is no listener, the picking should be * disabled on this viewable. * * @param listener */ public void addSelectionListener(CADSelectionListener listener) { selectionListeners.add(listener); } /** * Remove a selectionListener from this viewable. * As an optimisation, when there is no listener, the picking should be * disabled on this viewable. * @param listener */ public void removeSelectionListener(CADSelectionListener listener) { selectionListeners.remove(listener); } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#pick(com.sun.j3d.utils.picking.PickResult) */ @Override public void pick(PickViewable result) { Logger.getLogger(ViewableCAD.class.getName()).finest( "result.getGeometryArray().getUserData()=" + result.getGeometryArray().getUserData()); if(pick(result.getGeometryArray(), null)) fireSelectionChanged(); } private boolean pick(Geometry geom, Collection<Integer> previousSel) { if(geom == null) return false; Object o=geom.getUserData(); if((o instanceof FacePickingInfo)&&(selectionMode==FACE_SELECTION)) { FacePickingInfo fpi=(FacePickingInfo) o; setFaceSelected(fpi, checkToSelect(selectedFaces, fpi, previousSel)); return true; } else if((o instanceof EdgePickingInfo)&&(selectionMode==EDGE_SELECTION)) { EdgePickingInfo epi=(EdgePickingInfo) o; setEdgeSelected(epi, checkToSelect(selectedEdges, epi, previousSel)); return true; } else if((o instanceof VertexPickingInfo)&&(selectionMode==VERTEX_SELECTION)) { VertexPickingInfo epi=(VertexPickingInfo) o; setVertexSelected(epi, checkToSelect(selectedVertices, epi, previousSel)); return true; } return false; } /** * Check if an element must be selected or unselected * @param selected the current selection list of the current type * @param info the identifier to select or unselect * @param previousSel the list of selected element in the previous selection * event. This must be sepecified for SELECTION_INTERSECT mode and should be * null in other case. */ private boolean checkToSelect(Collection selected, CADPickingInfo info, Collection<Integer> previousSel) { boolean toReturn; switch(selectionAction) { case SELECTION_INVERT: toReturn = !selected.contains(info.id); break; case SELECTION_INTERSECT: toReturn = previousSel.contains(info.id); break; case SELECTION_ADD: toReturn = true; break; case SELECTION_REMOVE: toReturn = false; break; default: throw new IllegalStateException(); } return toReturn; } @Override public void pickArea(PickInfo[] result, Bounds bound) { HashSet<Geometry> as = new HashSet<Geometry>(); for(PickInfo pickInfo: result) { Node n = pickInfo.getNode(); if(n instanceof Shape3D) as.add(((Shape3D)n).getGeometry()); } Collection<Integer> previousSelection = null; //TODO dirty hack to implement selection intersection. //like this it will only work for faces if(selectionAction == SELECTION_INTERSECT) { previousSelection=new HashSet<Integer>(selectedFaces); unselectAll(); } boolean b = false; for(Geometry a: as) { if(pick(a, previousSelection)) b = true; } if(b) fireSelectionChanged(); } static private int[] integerCollectionToArray(Collection<Integer> collection) { int[] toReturn=new int[collection.size()]; int i=0; for(Integer n : collection) { toReturn[i]=n.intValue(); i++; } return toReturn; } /** * * @param faceID * @param status */ public void highlightFace(int faceID, boolean status) { highlightFace(faceID, status,true); } /** * * @param faceID * @param status * @param fireListeners */ public void highlightFace(int faceID, boolean status,boolean fireListeners) { FacePickingInfo fpi=facesInfo.get(new Integer(faceID)); setFaceSelected(fpi, status); if(fireListeners) fireSelectionChanged(); } /** * * @param edgeID * @param status */ public void highlightEdge(int edgeID, boolean status) { highlightEdge(edgeID, status, true); } /** * * @param edgeID * @param status * @param fireListeners */ public void highlightEdge(int edgeID, boolean status,boolean fireListeners) { EdgePickingInfo epi=edgesInfo.get(new Integer(edgeID)); setEdgeSelected(epi, status); if(fireListeners) fireSelectionChanged(); } public void highlightVertex(int vertexID, boolean status) { highlightVertex(vertexID, status, true); } public void highlightVertex(int vertexID, boolean status,boolean fireListeners) { VertexPickingInfo epi=verticesInfo.get(new Integer(vertexID)); setVertexSelected(epi, status); if(fireListeners) fireSelectionChanged(); } protected void setFaceSelected(FacePickingInfo fpi, boolean selected) { if(selected) { fpi.getMaterial1().setDiffuseColor(SELECTED_FACE_COLOR); if(fpi.getMaterial2() != null) fpi.getMaterial2().setDiffuseColor(SELECTED_FACE_COLOR); selectedFaces.add(fpi.getID()); } else { fpi.getMaterial1().setDiffuseColor(fpi.getColor1()); if(fpi.getMaterial2() != null) fpi.getMaterial2().setDiffuseColor(fpi.getColor2()); selectedFaces.remove(fpi.getID()); } } private void setEdgeSelected(EdgePickingInfo epi, boolean selected) { if(selected) { ColoringAttributes ca=new ColoringAttributes(); ca.setColor(new Color3f(Color.RED)); epi.appearance.setColoringAttributes(ca); selectedEdges.add(epi.getID()); } else { epi.appearance.setColoringAttributes(epi.coloringAttributes); selectedEdges.remove(epi.getID()); } } private void setVertexSelected(VertexPickingInfo epi, boolean selected) { if(selected) { ColoringAttributes ca=new ColoringAttributes(); ca.setColor(new Color3f(Color.YELLOW)); PointAttributes pa=new PointAttributes(5f, false); Appearance a=new Appearance(); a.setPointAttributes(pa); a.setColoringAttributes(ca); epi.shape3D.setAppearance(a); selectedVertices.add(epi.getID()); } else { epi.shape3D.setAppearance(epi.appearance); selectedVertices.remove(epi.getID()); } } @Override protected void fireSelectionChanged() { for(Object alistener : selectionListeners) { if(alistener instanceof CADSelectionListener){ CADSelectionListener cadlistener=(CADSelectionListener) alistener; CADSelection[] cs=new CADSelection[]{new CADSelection(0, integerCollectionToArray(selectedFaces), new int[0], new int[0])}; cadlistener.selectionChanged(cs); } else if(alistener instanceof SelectionListener){ ((SelectionListener)alistener).selectionChanged(); } } } public CADSelection[] getSelection(){ return new CADSelection[]{new CADSelection(0, integerCollectionToArray(selectedFaces),integerCollectionToArray(selectedEdges) , integerCollectionToArray(selectedVertices))}; } private Node createEdgesNode(CADDomain domain) { Iterator<float[]> it=domain.getEdgeIterator(); BranchGroup toReturn=new BranchGroup(); toReturn.setCapability(BranchGroup.ALLOW_DETACH); org.jcae.viewer3d.MarkUtils.setPickable(toReturn,true); edgesInfo=new HashMap<Integer, EdgePickingInfo>(); int n=0; while(it.hasNext()) { float[] coordinates=it.next(); LineStripArray lsa=new LineStripArray(coordinates.length/3, GeometryArray.COORDINATES, new int[]{coordinates.length/3}); lsa.setCapability(GeometryArray.ALLOW_COLOR_READ); lsa.setCapability(GeometryArray.ALLOW_COLOR_WRITE); lsa.setCapability(GeometryArray.ALLOW_COORDINATE_READ); lsa.setCapability(GeometryArray.ALLOW_COUNT_READ); lsa.setCapability(GeometryArray.ALLOW_FORMAT_READ); lsa.setCoordinates(0, coordinates); Shape3D shape3d=new Shape3D(lsa); Appearance a=new Appearance(); a.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE); ColoringAttributes ca=new ColoringAttributes(); if(domain instanceof ColoredDomain) { ColoredDomain d=(ColoredDomain)domain; ca.setColor(new Color3f(d.getColor())); } a.setLineAttributes(lineAttributes); a.setColoringAttributes(ca); shape3d.setAppearance(a); shape3d.setCapability(Shape3D.ALLOW_GEOMETRY_READ); shape3d.setCapability(Node.ALLOW_PICKABLE_WRITE); toReturn.addChild(shape3d); //Build Picking Data EdgePickingInfo epi=new EdgePickingInfo(n,a,ca); edgesInfo.put(new Integer(n), epi); lsa.setUserData(epi); n++; } return toReturn; } private Node createFacesNode(CADDomain domain) { Iterator<FaceMesh> it=domain.getFaceIterator(); BranchGroup toReturn=new BranchGroup(); toReturn.setCapability(BranchGroup.ALLOW_DETACH); int n=0; facesInfo=new HashMap<Integer, FacePickingInfo>(); while(it.hasNext()) { FaceMesh fm=it.next(); //Case of an unmeshed face if(fm.getNodes().length==0){ facesInfo.put(n, new FacePickingInfo(n)); n++; continue; } GeometryInfo gi=new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); gi.setCoordinates(fm.getNodes()); gi.setCoordinateIndices(fm.getMesh()); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); Stripifier st = new Stripifier(); st.stripify(gi); GeometryArray g=gi.getGeometryArray(); g.setCapability(GeometryArray.ALLOW_COUNT_READ); g.setCapability(GeometryArray.ALLOW_FORMAT_READ); g.setCapability(GeometryArray.ALLOW_COORDINATE_READ); g.setCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ); Color faceColor=null; if(domain instanceof OCCFaceDomain) faceColor=((OCCFaceDomain)domain).getFaceColor(n); if(faceColor==null) { Shape3D shape3d=new Shape3D(g); Appearance a=new Appearance(); shape3d.setAppearance(a); a.setPolygonAttributes(polygonAttrFront); Material m1=new Material(); m1.setDiffuseColor(FRONT_FACE_COLOR); m1.setSpecularColor(new Color3f(Color.BLACK)); m1.setAmbientColor(FRONT_FACE_COLOR); m1.setCapability(Material.ALLOW_COMPONENT_WRITE); a.setMaterial(m1); shape3d.setCapability(Shape3D.ALLOW_GEOMETRY_READ); shape3d.setCapability(Node.ALLOW_PICKABLE_WRITE); toReturn.addChild(shape3d); shape3d=new Shape3D(g); a=new Appearance(); shape3d.setAppearance(a); a.setPolygonAttributes(polygonAttrBack); Material m2=new Material(); m2.setDiffuseColor(BACK_FACE_COLOR); m2.setSpecularColor(new Color3f(Color.BLACK)); m2.setAmbientColor(BACK_FACE_COLOR); m2.setCapability(Material.ALLOW_COMPONENT_WRITE); a.setMaterial(m2); shape3d.setCapability(Shape3D.ALLOW_GEOMETRY_READ); shape3d.setCapability(Node.ALLOW_PICKABLE_WRITE); toReturn.addChild(shape3d); //Build Picking Data FacePickingInfo fpi = new FacePickingInfo(n, m1, m2, FRONT_FACE_COLOR, BACK_FACE_COLOR); facesInfo.put(n, fpi); g.setUserData(fpi); } else { Shape3D shape3d=new Shape3D(g); Appearance a=new Appearance(); shape3d.setAppearance(a); a.setPolygonAttributes(polygonAttrNone); Material m1=new Material(); Color3f color=new Color3f( ((float)faceColor.getRed())/255 ,((float)faceColor.getGreen())/255 ,((float)faceColor.getBlue())/255); m1.setAmbientColor(color); m1.setDiffuseColor(color); m1.setSpecularColor(color); m1.setCapability(Material.ALLOW_COMPONENT_WRITE); a.setMaterial(m1); shape3d.setCapability(Shape3D.ALLOW_GEOMETRY_READ); shape3d.setCapability(Node.ALLOW_PICKABLE_WRITE); toReturn.addChild(shape3d); //Build Picking Data FacePickingInfo fpi = new FacePickingInfo(n, m1 ,color); facesInfo.put(n, fpi); g.setUserData(fpi); } n++; } return toReturn; } private Node createVerticesNode(CADDomain domain) { float pointSize=((Float)domain.getMarksTypes()[0]).floatValue(); float[] points=domain.getMarks(null); BranchGroup toReturn=new BranchGroup(); toReturn.setCapability(BranchGroup.ALLOW_DETACH); int n=0; verticesInfo=new HashMap<Integer, VertexPickingInfo>(); PointAttributes pat=new PointAttributes(pointSize, false); for(int i=0; i<points.length/3; i++) { PointArray pa=new PointArray(1, GeometryArray.COORDINATES); pa.setCoordinates(0, new float[]{points[3*i], points[3*i+1], points[3*i+2]}); Appearance a=new Appearance(); a.setPointAttributes(pat); ColoringAttributes ca=new ColoringAttributes( new Color3f(Color.RED), ColoringAttributes.FASTEST); a.setColoringAttributes(ca); Shape3D s3d = new Shape3D(pa, a); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); s3d.setCapability(Node.ALLOW_PICKABLE_WRITE); //return a BranchGroup which is the only detachable node. toReturn.addChild(s3d); //Build Picking Data VertexPickingInfo epi=new VertexPickingInfo(n,a, s3d); verticesInfo.put(new Integer(n), epi); pa.setUserData(epi); n++; } return toReturn; } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#getBranchGroup(org.jcae.viewer3d.View) */ @Override public Node getJ3DNode() { return branchGroup; } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#unselectAll() */ @Override public void unselectAll() { int[] ids=integerCollectionToArray(selectedFaces); for(int i=0; i<ids.length; i++) { FacePickingInfo fpi=facesInfo.get(new Integer(ids[i])); setFaceSelected(fpi, false); } ids=integerCollectionToArray(selectedEdges); for(int i=0; i<ids.length; i++) { EdgePickingInfo epi=edgesInfo.get(new Integer(ids[i])); setEdgeSelected(epi, false); } ids=integerCollectionToArray(selectedVertices); for(int i=0; i<ids.length; i++) { VertexPickingInfo epi=verticesInfo.get(new Integer(ids[i])); setVertexSelected(epi, false); } fireSelectionChanged(); } public void setName(String name) { this.name=name; } @Override public String toString() { return name; } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#addSelectionListener(org.jcae.viewer3d.SelectionListener) */ @Override public void addSelectionListener(SelectionListener listener) { selectionListeners.add(listener); } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#removeSelectionListener(org.jcae.viewer3d.SelectionListener) */ @Override public void removeSelectionListener(SelectionListener listener) { selectionListeners.remove(listener); } public void setLineWidth(float lineWidth) { lineAttributes.setLineWidth(lineWidth); } /** @param s SELECTION_INVERT, SELECTION_ADD, SELECTION_REMOVE */ public void setSelectionAction(int s) { selectionAction = s; } public int getSelectionAction() { return selectionAction; } }