/* * 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 2007, by EADS France */ package org.jcae.viewer3d.fe; import java.awt.Color; import java.awt.Component; import java.util.*; import java.util.logging.Logger; import javax.media.j3d.*; import javax.swing.JPanel; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import org.jcae.viewer3d.DomainProvider; import org.jcae.viewer3d.PickViewable; import org.jcae.viewer3d.ViewableAdaptor; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; import com.sun.j3d.utils.picking.PickIntersection; /** * @author Jerome Robert * @todo implements all methods */ public class ViewableFE extends ViewableAdaptor { private final static Logger LOGGER=Logger.getLogger(ViewableFE.class.getName()); public static final byte PICK_DOMAIN = 2; public static final byte PICK_NODE = 1; private FEProvider provider; private Map<Integer, Boolean> visibleDomain; private BranchGroup branchGroup; private Shape3D nodeSelectionShape=new Shape3D(); private Map<Integer, NodeSelectionImpl> nodeSelections=new HashMap<Integer, NodeSelectionImpl>(); private Map<Integer, BranchGroup> domainIDToBranchGroup=new HashMap<Integer, BranchGroup>(); private Collection<Integer> selectedDomains=new HashSet<Integer>(); private String name; private short pickingMode=PICK_DOMAIN; private boolean showShapeLine=true; 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")); final private static PolygonAttributes FILL_POLYGON_ATTR=new PolygonAttributes( PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 2.0f * zFactorAbs, true, zFactorRel); final private static PolygonAttributes LINE_POLYGON_ATTR=new PolygonAttributes( PolygonAttributes.POLYGON_LINE, PolygonAttributes.CULL_NONE, zFactorAbs); /** * */ public ViewableFE(FEProvider provider) { this.provider=provider; visibleDomain=new HashMap<Integer, Boolean>(); int[] ids=provider.getDomainIDs(); for(int i=0; i<ids.length; i++) { visibleDomain.put(new Integer(ids[i]), Boolean.TRUE); } branchGroup=new BranchGroup(); branchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); branchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); branchGroup.setCapability(Group.ALLOW_CHILDREN_READ); Appearance app=new Appearance(); PointAttributes pa=new PointAttributes(4f, false); app.setPointAttributes(pa); nodeSelectionShape.setAppearance(app); } /* (non-Javadoc) * @see jcae.viewer3d.mesh.ViewableMesh#getDomainProvider() */ @Override public DomainProvider getDomainProvider() { return provider; } /* (non-Javadoc) * @see jcae.viewer3d.Viewable#domainsChanged(java.util.Collection) */ @Override public void domainsChangedPerform(int[] ids) { if(ids!=null) { for(int i=0; i<ids.length; i++) { //If the domain already exists remove it before readding it BranchGroup dbg=domainIDToBranchGroup.get(new Integer(ids[i])); if(dbg!=null) { branchGroup.removeChild(dbg); domainIDToBranchGroup.remove(new Integer(ids[i])); } //If the domain is not visible do not readd it Boolean b=visibleDomain.get(new Integer(ids[i])); if(b==null) { visibleDomain.put(new Integer(ids[i]), Boolean.TRUE); b=Boolean.TRUE; } if(b.booleanValue()) { LOGGER.finest("<Loading domain "+ids[i]+">"); createBranchGroup((FEDomain)provider.getDomain(ids[i])); LOGGER.finest("</Loading domain "+ids[i]+">"); } } } else //for a null parameter remove all domains { domainIDToBranchGroup.clear(); branchGroup.removeAllChildren(); ids=getDomainProvider().getDomainIDs(); for(int i=0; i<ids.length; i++) { Boolean b=visibleDomain.get(new Integer(ids[i])); if(b==null) { visibleDomain.put(new Integer(ids[i]), Boolean.TRUE); b=Boolean.TRUE; } if(b.booleanValue()) { LOGGER.finest("<Loading domain "+ids[i]+">"); createBranchGroup((FEDomain)provider.getDomain(ids[i])); LOGGER.finest("</Loading domain "+ids[i]+">"); } } } } private void createBranchGroup(FEDomain d) { if(d.getNumberOfTria3()>0) branchGroup.addChild(createTriaBranchGroup(d, false)); if(d.getNumberOfTria6()>0) branchGroup.addChild(createTriaBranchGroup(d, true)); if(d.getNumberOfQuad4()>0) branchGroup.addChild(createQuadBranchGroup(d, false)); else if(d.getNumberOfBeam2()>0) branchGroup.addChild(createBeamBranchGroup(d)); } /** Workaround to buggy auto bounding box of GeomInfo */ public static BoundingBox computeBoundingBox(float[] nodes) { float[] min=new float[]{Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE}; float[] max=new float[]{-Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE}; for(int i=0; i<nodes.length; i+=3) { for(int j=0; j<3; j++) { if(nodes[i+j]<min[j]) min[j]=nodes[i+j]; if(nodes[i+j]>max[j]) max[j]=nodes[i+j]; } } return new BoundingBox( new Point3d(new Point3f(min)), new Point3d(new Point3f(max))); } private Node createBeamBranchGroup(FEDomain d) { BranchGroup bg=new BranchGroup(); IndexedLineArray ila=new IndexedLineArray( d.getNumberOfNodes(), GeometryArray.COORDINATES, d.getNumberOfBeam2()*2); ila.setCoordinates(0, d.getNodes()); ila.setCoordinateIndices(0, d.getBeam2()); Appearance app=new Appearance(); LineAttributes la=new LineAttributes(3f, LineAttributes.PATTERN_SOLID, false); ColoringAttributes ca=new ColoringAttributes(new Color3f(d.getColor()), ColoringAttributes.FASTEST); app.setLineAttributes(la); app.setColoringAttributes(ca); Shape3D s3d=new Shape3D(ila, app); bg.addChild(s3d); domainIDToBranchGroup.put(new Integer(d.getID()), bg); return bg; } /* (non-Javadoc) * @see jcae.viewer3d.Viewable#setDomainVisible(java.util.Map) */ @Override public void setDomainVisible(Map<Integer, Boolean> map) { Iterator<Map.Entry<Integer, Boolean>> it=map.entrySet().iterator(); while(it.hasNext()) { Map.Entry<Integer, Boolean> entry= it.next(); Boolean newStatus=entry.getValue(); Boolean oldStatus=visibleDomain.get(entry.getKey()); if(oldStatus==null) oldStatus=Boolean.FALSE; if(!newStatus.booleanValue() && oldStatus.booleanValue()) { BranchGroup dbg=domainIDToBranchGroup.get(entry.getKey()); if(dbg!=null) { branchGroup.removeChild(dbg); } } else if(newStatus.booleanValue() && !oldStatus.booleanValue()) { BranchGroup dbg=domainIDToBranchGroup.get(entry.getKey()); if(dbg!=null) { branchGroup.addChild(dbg); } else { FEDomain d=(FEDomain) provider.getDomain(entry.getKey().intValue()); createBranchGroup(d); } } visibleDomain.put(entry.getKey(), newStatus); } } /** * Return a color palette showing the color used to represent the result * in this viewable. * The palette include numerical graduation. * The caller may resize the returned component to get an horizontal or a * vertical palette. * @return */ public Component getResultPalette() { //TODO return new JPanel(); } public void setPickingMode(byte mode) { pickingMode=mode; } /* * (non-Javadoc) * @see org.jcae.viewer3d.Viewable#pick(com.sun.j3d.utils.picking.PickResult) */ @Override public void pick(PickViewable result) { System.out.println("picked node=" + result.getObject()); LOGGER.finest("result=" + result); LOGGER.finest("result.getGeometryArray().getUserData()=" + result.getGeometryArray().getUserData()); Integer o = (Integer) result.getGeometryArray().getUserData(); int domainID = o.intValue(); switch (pickingMode) { case PICK_NODE : PickIntersection pi = result.getIntersection(); int[] ids = pi.getPrimitiveVertexIndices(); pickdNode(ids[0] / ids.length, (byte) pi .getClosestVertexIndex(), pi.getClosestVertexCoordinates(), domainID); break; case PICK_DOMAIN : boolean toSelect = !selectedDomains.contains(new Integer(domainID)); setSelectedDomain(domainID, toSelect); fireSelectionChanged(); break; } } private void pickdNode(int triaID, byte nodeID, Point3d point3d, int domainID) { NodeSelectionImpl ns = nodeSelections .get(new Integer(domainID)); if (ns == null) { ns = new NodeSelectionImpl(domainID); nodeSelections.put(new Integer(domainID), ns); } boolean toSelect = !ns.containsNode(triaID, nodeID); if (toSelect) { ns.addNode(triaID, nodeID); PointArray pa = new PointArray(1, GeometryArray.COORDINATES); nodeSelectionShape.addGeometry(pa); fireSelectionChanged(); } else { // TODO Implement unselect } } private void setSelectedNode(int triaID, byte nodeID, Point3d point3d, int domainID, boolean selected) { if(selected) { NodeSelectionImpl ns=nodeSelections.get(new Integer(domainID)); if(ns==null) { ns=new NodeSelectionImpl(domainID); nodeSelections.put(new Integer(domainID), ns); } ns.addNode(triaID, nodeID); PointArray pa=new PointArray(1, GeometryArray.COORDINATES); nodeSelectionShape.addGeometry(pa); fireSelectionChanged(); } else { //TODO Implement unselect } } public void highlight(int domainID, boolean selected) { highlight(domainID, selected, true); } public void highlight(int domainID, boolean selected,boolean fireListeners) { setSelectedDomain(domainID, selected); if(fireListeners) fireSelectionChanged(); } private void setSelectedDomain(int domainID, boolean selected) { BranchGroup bg=domainIDToBranchGroup.get(new Integer(domainID)); if(bg==null) //test for empty groups return; if(showShapeLine) { LOGGER.finest("Changing color of domain nr"+domainID+" to red. bg="+bg); Color colorToSet; if(selected) { colorToSet=Color.RED; selectedDomains.add(new Integer(domainID)); } else { colorToSet=Color.WHITE; selectedDomains.remove(new Integer(domainID)); } ((Shape3D)bg.getChild(bg.numChildren()-1)).getAppearance(). getColoringAttributes().setColor(new Color3f(colorToSet)); } } private int[] getTriaIndices(FEDomain domain, boolean parabolic) { if(parabolic) { return domain.getTria6(); } return domain.getTria3(); } private IndexedTriangleArray getGeomForTrianglesGroup(FEDomain domain, float[] nodes, boolean parabolic) { if(domain.getNumberOfNodes()==0 || (domain.getNumberOfTria3()==0 && !parabolic) || (domain.getNumberOfTria6()==0 && parabolic)) return null; int[] tria3=getTriaIndices(domain, parabolic); IndexedTriangleArray geom; if(showShapeLine) { geom = new IndexedTriangleArray(nodes.length / 3, GeometryArray.COORDINATES, tria3.length); geom.setCoordinateIndices(0, tria3); geom.setCoordinates(0, nodes); } else { GeometryInfo gi=new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); gi.setCoordinates(nodes); gi.setCoordinateIndices(tria3); NormalGenerator ng=new NormalGenerator(0); ng.generateNormals(gi); geom=(IndexedTriangleArray) gi.getIndexedGeometryArray(); } geom.setCapability(GeometryArray.ALLOW_COUNT_READ); geom.setCapability(GeometryArray.ALLOW_FORMAT_READ); geom.setCapability(GeometryArray.ALLOW_COORDINATE_READ); geom.setCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ); geom.setUserData(new Integer(domain.getID())); return geom; } private int[] getQuadIndices(FEDomain domain, boolean parabolic) { if(parabolic) throw new IllegalArgumentException("Parabolic quad not yet supported"); return domain.getQuad4(); } private IndexedQuadArray getGeomForQuadsGroup(FEDomain domain, float[] nodes, boolean parabolic) { if(domain.getNumberOfNodes()==0 || ((domain.getNumberOfQuad4()==0) != parabolic)) return null; int[] quad4=getQuadIndices(domain, parabolic); IndexedQuadArray geom; if(showShapeLine) { geom = new IndexedQuadArray(nodes.length / 3, GeometryArray.COORDINATES, quad4.length); geom.setCoordinateIndices(0, quad4); geom.setCoordinates(0, nodes); } else { GeometryInfo gi=new GeometryInfo(GeometryInfo.QUAD_ARRAY); gi.setCoordinates(nodes); gi.setCoordinateIndices(quad4); NormalGenerator ng=new NormalGenerator(0); ng.generateNormals(gi); geom=(IndexedQuadArray) gi.getIndexedGeometryArray(); } geom.setCapability(GeometryArray.ALLOW_COUNT_READ); geom.setCapability(GeometryArray.ALLOW_FORMAT_READ); geom.setCapability(GeometryArray.ALLOW_COORDINATE_READ); geom.setCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ); geom.setUserData(new Integer(domain.getID())); return geom; } /** * Creates a Java3D BranchGroup whcih represents a group. * It creates two Java3D Shapes3D : one for polygons, one for edges. * @param the Java3D geometry of a Group. */ private BranchGroup createTriaBranchGroup(FEDomain domain, boolean parabolic) { float[] nodes=domain.getNodes(); //bounding box computed from GeomInfo are buggy so we do it ourself BoundingBox bb=computeBoundingBox(nodes); IndexedTriangleArray geom = getGeomForTrianglesGroup(domain, nodes, parabolic); if(geom==null) return new BranchGroup(); return createIndexedBranchGroup(domain, geom, bb, parabolic); } private BranchGroup createQuadBranchGroup(FEDomain domain, boolean parabolic) { float[] nodes=domain.getNodes(); //bounding box computed from GeomInfo are buggy so we do it ourself BoundingBox bb=computeBoundingBox(nodes); IndexedQuadArray geom = getGeomForQuadsGroup(domain, nodes, parabolic); if(geom==null) return new BranchGroup(); return createIndexedBranchGroup(domain, geom, bb, parabolic); } private BranchGroup createIndexedBranchGroup(FEDomain domain, IndexedGeometryArray geom, BoundingBox bb, boolean parabolic) { BranchGroup toReturn = new BranchGroup(); Appearance shapeFillAppearance = new Appearance(); shapeFillAppearance.setPolygonAttributes(FILL_POLYGON_ATTR); Shape3D shapeFill = new Shape3D(geom, shapeFillAppearance); shapeFill.setBoundsAutoCompute(false); shapeFill.setBounds(bb); if(showShapeLine) { Color c=domain.getColor().darker(); if(parabolic) c=c.brighter(); else c=c.darker(); shapeFillAppearance.setColoringAttributes(new ColoringAttributes( new Color3f(domain.getColor().darker()), ColoringAttributes.FASTEST)); } else { Material m=new Material(); m.setAmbientColor(new Color3f(domain.getColor())); shapeFillAppearance.setMaterial(m); shapeFill.setCapability(Shape3D.ALLOW_APPEARANCE_READ); shapeFillAppearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ); } shapeFill.setCapability(Shape3D.ALLOW_GEOMETRY_READ); toReturn.addChild(shapeFill); if(showShapeLine) { Appearance shapeLineAppearance = new Appearance(); shapeLineAppearance.setPolygonAttributes(LINE_POLYGON_ATTR); ColoringAttributes ca = new ColoringAttributes(new Color3f(Color.WHITE), ColoringAttributes.FASTEST); ca.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE); shapeLineAppearance.setColoringAttributes(ca); Shape3D shapeLine = new Shape3D(geom, shapeLineAppearance); shapeLine.setBoundsAutoCompute(false); shapeLine.setBounds(bb); shapeLine.setPickable(false); shapeLine.setCapability(Shape3D.ALLOW_APPEARANCE_READ); shapeLineAppearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ); toReturn.addChild(shapeLine); } toReturn.setCapability(Group.ALLOW_CHILDREN_READ); toReturn.setCapability(BranchGroup.ALLOW_DETACH); domainIDToBranchGroup.put(new Integer(domain.getID()), toReturn); return toReturn; } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#getBranchGroup() */ @Override public Node getJ3DNode() { if(branchGroup.numChildren()==0) domainsChanged(getDomainProvider().getDomainIDs()); return branchGroup; } /* (non-Javadoc) * @see org.jcae.viewer3d.Viewable#unselectAll() */ @Override public void unselectAll() { for(Integer Id: selectedDomains) setSelectedDomain(Id.intValue(), false); nodeSelections.clear(); nodeSelectionShape.removeAllGeometries(); fireSelectionChanged(); } public void setName(String name) { this.name=name; } @Override public String toString() { return name; } public int[] getSelectedDomains() { int[] toReturn=new int[selectedDomains.size()]; int i=0; for (Integer Id: selectedDomains) { toReturn[i]=Id.intValue(); i++; } return toReturn; } public NodeSelection[] getSelectedNodes() { NodeSelection[] toReturn=new NodeSelection[nodeSelections.size()]; Iterator<NodeSelectionImpl> it=nodeSelections.values().iterator(); int i=0; while(it.hasNext()) { toReturn[i++]=(NodeSelection) it.next().clone(); } return toReturn; } /** * if true the border of the element will be displayed else elements * are render with shading. The default value is true. */ public void setShowShapeLine(boolean showShapeLine) { this.showShapeLine = showShapeLine; } }