/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2007,2008, by EADS France This library 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 library 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jcae.viewer3d; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Iterator; import java.lang.ref.SoftReference; import gnu.trove.set.hash.TIntHashSet; import javax.media.j3d.*; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import org.jcae.mesh.amibe.ds.Mesh; import org.jcae.mesh.amibe.ds.Vertex; import org.jcae.mesh.amibe.traits.MeshTraitsBuilder; import org.jcae.mesh.oemm.OEMM; import org.jcae.mesh.oemm.MeshReader; import org.jcae.viewer3d.bg.ViewableBG; import java.util.logging.Level; import java.util.logging.Logger; /** * Dynamically hide and show voxel in a OEMM viewer */ public class OEMMBehavior extends Behavior { private static Logger logger = Logger.getLogger(OEMMBehavior.class.getName()); private static final int DEFAULT_MAX_TRIANGLES_NBR = -1; private boolean frozen = false; private MeshReader coarseReader; private MeshReader fineReader; private MeshTraitsBuilder mtb = new MeshTraitsBuilder(); private static class ViewHolder { private BranchGroup viewElem; private int id; private Mesh mesh; private int nrTriangles; public ViewHolder(int id, Mesh mesh) { super(); this.id = id; this.mesh = mesh; this.nrTriangles = mesh.getTriangles().size(); } public ViewHolder(int id, BranchGroup viewElem) { super(); this.viewElem = viewElem; this.id = id; } public BranchGroup getViewElement() { return viewElem; } public void setViewElem(BranchGroup viewElem) { this.viewElem = viewElem; } public int getId() { return id; } public Mesh getMesh() { return mesh; } } private static class VoxelSortHelper implements Comparable<VoxelSortHelper> { private float distance; private int voxelIndex; public VoxelSortHelper(float distance, int voxelIndex) { super(); this.distance = distance; this.voxelIndex = voxelIndex; } public int compareTo(VoxelSortHelper o) { return Float.compare(this.distance, o.distance); } public int getVoxelIndex() { return voxelIndex; } } interface ChangeListener { void stateChanged(OEMMBehavior behaviour); } /** Utility field holding list of ChangeListeners. */ private transient ArrayList<ChangeListener> changeListenerList; /** * The square of the minimal distance between the eye and a displayed * OEMM voxel */ private double d2limit; private long maxNumberOfTriangles; private OEMM oemm; private boolean oemmActive; private View view; private Point3d[] voxels; private WakeupCriterion wakeupFrame; private WakeupCriterion wakeupTransf; private Map<Integer, ViewHolder> coarseOemmNodeId2BranchGroup = new HashMap<Integer, ViewHolder>(); private Map<Integer, ViewHolder> visibleFineOemmNodeId2BranchGroup = new HashMap<Integer, ViewHolder>(); private SoftReference<ViewHolder>[] cacheOemmNodeId2BranchGroup ; private BranchGroup visibleMeshBranchGroup = new BranchGroup(); public OEMMBehavior(View canvas, OEMM oemm, OEMM coarseOEMM) { visibleMeshBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND); visibleMeshBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE); cacheOemmNodeId2BranchGroup = new SoftReference[oemm.getNumberOfLeaves()]; canvas.add(new ViewableBG(visibleMeshBranchGroup)); boolean cloneBoundaryTriangles = Boolean.getBoolean("org.jcae.viewer3d.OEMMBehavior.cloneBoundaryTriangles"); fineReader = new MeshReader(oemm); fineReader.setLoadNonReadableTriangles(true); mtb.addTriangleList(); coarseReader = new MeshReader(coarseOEMM); coarseReader.setLoadNonReadableTriangles(cloneBoundaryTriangles); coarseReader.buildMeshes(mtb); for(int i = 0, n = coarseOEMM.getNumberOfLeaves(); i < n; i++) { Integer II = Integer.valueOf(i); Mesh mesh = coarseReader.getMesh(i); ViewHolder vh = new ViewHolder(i, mesh); vh.setViewElem(OEMMViewer.meshOEMM(mesh)); coarseOemmNodeId2BranchGroup.put(II, vh); addViewHolderToBranchGroup(II, coarseOemmNodeId2BranchGroup); } setSchedulingBounds(new BoundingSphere( new Point3d(), Double.MAX_VALUE)); double[] coords=oemm.getCoords(true); computeVoxels(canvas, coords); this.oemm=oemm; d2limit=2*(coords[0]-coords[6*4*3-6]); wakeupFrame=new WakeupOnElapsedFrames(1); wakeupTransf=new WakeupOnTransformChange( view.getViewingPlatform().getViewPlatformTransform()); maxNumberOfTriangles = Long.getLong("org.jcae.viewer3d.OEMMBehavior.maxNumberOfTriangles", DEFAULT_MAX_TRIANGLES_NBR).longValue(); if (logger.isLoggable(Level.INFO)) { logger.info("Maximal number of triangles: " + maxNumberOfTriangles); } } public Set<Integer> getIds() { return visibleFineOemmNodeId2BranchGroup.keySet(); } private void computeVoxels(View canvas, double[] coords) { final double[] values = new double[3]; view=canvas; voxels=new Point3d[coords.length/6/4/3]; for(int i=0; i<voxels.length; i++) { ViewHolder vh = coarseOemmNodeId2BranchGroup.get(Integer.valueOf(i)); Collection<Vertex> nodes = vh.mesh.getNodes(); if (getAveragePointForVertices(nodes, values)) { voxels[i]=new Point3d(values[0], values[1], values[2]); } else { int n=6*4*3*i; voxels[i]=new Point3d( (coords[n+0]+coords[n+6*4*3-6])/2, (coords[n+1]+coords[n+6*4*3-5])/2, (coords[n+2]+coords[n+6*4*3-4])/2); } vh.mesh = null; } } /** * Registers ChangeListener to receive events. * @param listener The listener to register. */ public synchronized void addChangeListener(ChangeListener listener) { if (changeListenerList == null ) { changeListenerList = new ArrayList<ChangeListener> (); } changeListenerList.add (listener); } /** * Notifies all registered listeners about the event. * @param object Parameter #1 of the <CODE>ChangeEvent<CODE> constructor. */ private void fireChangeListenerStateChanged() { ArrayList<ChangeListener> list; synchronized (this) { if (changeListenerList == null) return; list = (ArrayList<ChangeListener>)changeListenerList.clone (); } for (ChangeListener cl: list) cl.stateChanged(this); } @Override public void initialize() { d2limit=d2limit/Math.tan(view.getView().getFieldOfView()/2); d2limit=d2limit*d2limit; wakeupOn(wakeupTransf); } public boolean isOemmActive() { return oemmActive; } @Override public void processStimulus(Enumeration arg0) { if(arg0.nextElement() instanceof WakeupOnTransformChange) { wakeupOn(wakeupFrame); return; } if (!frozen) { final Set<Integer> ids = new HashSet<Integer>(); findVoxelsWithFineMesh(ids); showFineMesh(ids); } wakeupOn(wakeupTransf); } private void findVoxelsWithFineMesh(final Set<Integer> ids) { ViewPyramid vp=new ViewPyramid(view, scaleRectangle(view.getBounds(), 2.5)); List<VoxelSortHelper> helper = new ArrayList<VoxelSortHelper>(); long totalNumberTriangles = 0; //number of already visible vertices for(int i=0; i<voxels.length; i++) { if (voxels[i] == null) { continue; } double distance_2 = voxels[i].distanceSquared(vp.getEye()); if(distance_2 < d2limit && vp.intersect(voxels[i])) { if (maxNumberOfTriangles > 0) { double distanceFromCenter_2 = distanceOfPointFromLine(vp.getStartPoint(), vp.getEye(), voxels[i]); helper.add(new VoxelSortHelper((float) (distance_2 + distanceFromCenter_2 / distance_2), i)); } else { ids.add(Integer.valueOf(i)); } } } if (maxNumberOfTriangles > 0) { Collections.sort(helper); for (VoxelSortHelper voxel: helper) { long newTotalNumber = totalNumberTriangles + oemm.leaves[voxel.voxelIndex].tn; if (newTotalNumber < maxNumberOfTriangles ) { totalNumberTriangles = newTotalNumber; ids.add(Integer.valueOf(voxel.voxelIndex)); } else { break; } } } } /** * Computes squared distance of a point x0 * from a line (defined by x1 and x2) * t = - (x1 - x0)* (x2 - x1) / |x2 - x1|^2; * d^2 = [x1 + (x2 - x2) * t - x0]^2 * * @param startPoint is x1 * @param eye is x2 * @param pt is x0 * @return */ private double distanceOfPointFromLine(Point3d startPoint, Point3d eye, Point3d pt) { Vector3d x1 = new Vector3d(startPoint); Vector3d x2 = new Vector3d(eye); Vector3d x0 = new Vector3d(pt); Vector3d temp = (Vector3d) x2.clone(); temp.sub(x1); double absolute = temp.length(); temp = (Vector3d) x1.clone(); temp.sub(x0); Vector3d temp2 = (Vector3d) x2.clone(); temp2.sub(x1); double t = - temp.dot(temp2) / (absolute * absolute); temp = (Vector3d) x2.clone(); x2.sub(x1); x2.scale(t); temp.add(x1); temp.sub(x0); double result = temp.lengthSquared(); return result; } private void showFineMesh(final Set<Integer> ids) { if (logger.isLoggable(Level.INFO)) { logger.info("Fine occtree nodes> " + ids); } boolean containsAll = (ids.size() > 0); if (containsAll) { containsAll = getIds().containsAll(ids); } if(!containsAll) { oemmActive=ids.size()>0; if (logger.isLoggable(Level.FINE)) { logger.fine("We will show fine mesh for nodes: " + ids); } showCoarseNodes(ids); if(ids.size()>0) { showFineNodes(ids); } fireChangeListenerStateChanged(); } } /** * Removes ChangeListener from the list of listeners. * @param listener The listener to remove. */ public synchronized void removeChangeListener(ChangeListener listener) { if (changeListenerList != null ) { changeListenerList.remove (listener); } } /** * @param rectangle The rectangle to scale * @param factor The factor to apply * @return The rectangle which was specified as input */ private Rectangle scaleRectangle(Rectangle rectangle, double factor) { double k=(1-factor)/2; rectangle.x += rectangle.width*k; rectangle.y += rectangle.height*k; rectangle.width = (int) (rectangle.width*factor); rectangle.height = (int) (rectangle.height*factor); return rectangle; } private void addViewHolderToBranchGroup(Integer id, Map<Integer, ViewHolder> map) { if (logger.isLoggable(Level.FINE)) { logger.fine("addViewHolderToBranchGroup> id:" + id + ", coarse:" + (map == coarseOemmNodeId2BranchGroup)); } ViewHolder vh = map.get(id); BranchGroup branchGroup = vh.getViewElement(); if (!branchGroup.getCapability(BranchGroup.ALLOW_DETACH)) branchGroup.setCapability(BranchGroup.ALLOW_DETACH); visibleMeshBranchGroup.addChild(branchGroup); } private void removeViewHolderFromBranchGroup(Integer id, Map<Integer, ViewHolder> map) { if (logger.isLoggable(Level.FINE)) { logger.fine("removeViewHolderFromBranchGroup> id:" + id + ", coarse:" + (map == coarseOemmNodeId2BranchGroup)); } //branchGroup.setCapability(BranchGroup.ALLOW_DETACH); ViewHolder vh = map.get(id); visibleMeshBranchGroup.removeChild(vh.getViewElement()); } private void showCoarseNodes(Set<Integer> exceptSet) { // Use an Iterator because visibleFineOemmNodeId2BranchGroup is modified within this loop, for (Iterator<Map.Entry<Integer, ViewHolder>> it = visibleFineOemmNodeId2BranchGroup.entrySet().iterator(); it.hasNext(); ) { Integer arg0 = it.next().getKey(); if (!exceptSet.contains(arg0)) { addViewHolderToBranchGroup(arg0, coarseOemmNodeId2BranchGroup); removeViewHolderFromBranchGroup(arg0, visibleFineOemmNodeId2BranchGroup); it.remove(); } } } private void showFineNodes(final Set<Integer> ids) { int nrTriangles = 0; for (Integer arg0: ids) { ViewHolder vh = getFineMeshFromCache(arg0.intValue()); if (!visibleFineOemmNodeId2BranchGroup.containsKey(arg0)) { visibleFineOemmNodeId2BranchGroup.put(arg0, vh); addViewHolderToBranchGroup(arg0, visibleFineOemmNodeId2BranchGroup); removeViewHolderFromBranchGroup(arg0, coarseOemmNodeId2BranchGroup); } nrTriangles += vh.nrTriangles; } logger.info("Number of triangles in fine mesh: " + nrTriangles); } private ViewHolder getFineMeshFromCache(int arg0) { ViewHolder vh = null; if (cacheOemmNodeId2BranchGroup[arg0] != null) vh = cacheOemmNodeId2BranchGroup[arg0].get(); if (vh == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("finemesh node:" + arg0 + " is not loaded and I will load it."); } TIntHashSet set = new TIntHashSet(); set.add(arg0); Mesh mesh = fineReader.buildMesh(set); vh = new ViewHolder(arg0, OEMMViewer.meshOEMM(mesh)); vh.nrTriangles = mesh.getTriangles().size(); cacheOemmNodeId2BranchGroup[arg0] = new SoftReference<ViewHolder>(vh); } return vh; } /** * Computes average center of vertices * @param vertices * @param result vector for store result */ private boolean getAveragePointForVertices(Collection<Vertex> vertices, double[] result) { int count = 0; for (int i = 0; i < result.length; i++) { result[i] = 0.0; } for (Vertex v: vertices) { if (!v.isReadable()) continue; count++; double []coords = v.getUV(); for (int i = 0; i < result.length; i++) { result[i] += coords[i] ; } } if (count > 0) { double size = count; for (int i = 0; i < result.length; i++) { result[i] /= size; } } return count > 0; } public Point3d getVoxel(int arg0) { return voxels[arg0]; } public int getNumberOfCacheNodes() { int ret = 0; for (SoftReference<ViewHolder> sr: cacheOemmNodeId2BranchGroup) { if (sr != null && sr.get() != null) ret++; } return ret; } public int getNumberOfVisibleFineElements() { return visibleFineOemmNodeId2BranchGroup.size(); } public void switchFreeze() { frozen = !frozen; } }