001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    //
030    // MyRubberBandBoxRendererJ3D.java
031    //
032    
033    /*
034    VisAD system for interactive analysis and visualization of numerical
035    data.  Copyright (C) 1996 - 2002 Bill Hibbard, Curtis Rueden, Tom
036    Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
037    Tommy Jasmin.
038    
039    This library is free software; you can redistribute it and/or
040    modify it under the terms of the GNU Library General Public
041    License as published by the Free Software Foundation; either
042    version 2 of the License, or (at your option) any later version.
043    
044    This library is distributed in the hope that it will be useful,
045    but WITHOUT ANY WARRANTY; without even the implied warranty of
046    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
047    Library General Public License for more details.
048    
049    You should have received a copy of the GNU Library General Public
050    License along with this library; if not, write to the Free
051    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
052    MA 02111-1307, USA
053    */
054    
055    package edu.wisc.ssec.mcidasv.data.hydra;
056    
057    import java.awt.event.InputEvent;
058    import java.awt.event.WindowAdapter;
059    import java.awt.event.WindowEvent;
060    import java.rmi.RemoteException;
061    import java.util.Enumeration;
062    import java.util.Vector;
063    
064    import javax.media.j3d.Appearance;
065    import javax.media.j3d.BranchGroup;
066    import javax.media.j3d.GeometryArray;
067    import javax.media.j3d.Group;
068    import javax.media.j3d.Shape3D;
069    import javax.swing.BoxLayout;
070    import javax.swing.JFrame;
071    import javax.swing.JPanel;
072    
073    import visad.BadDirectManipulationException;
074    import visad.CellImpl;
075    import visad.CoordinateSystem;
076    import visad.DataDisplayLink;
077    import visad.DataReference;
078    import visad.DataReferenceImpl;
079    import visad.Display;
080    import visad.DisplayImpl;
081    import visad.DisplayRealType;
082    import visad.DisplayTupleType;
083    import visad.FlatField;
084    import visad.FunctionType;
085    import visad.GraphicsModeControl;
086    import visad.Gridded2DSet;
087    import visad.Gridded3DSet;
088    import visad.Integer2DSet;
089    import visad.Real;
090    import visad.RealTupleType;
091    import visad.RealType;
092    import visad.ScalarMap;
093    import visad.ScalarType;
094    import visad.Set;
095    import visad.ShadowType;
096    import visad.Unit;
097    import visad.VisADException;
098    import visad.VisADLineStripArray;
099    import visad.VisADRay;
100    import visad.java3d.DirectManipulationRendererJ3D;
101    import visad.java3d.DisplayImplJ3D;
102    import visad.java3d.ShadowTypeJ3D;
103    
104    /**
105       RubberBandBoxRendererJ3D is the VisAD class for direct
106       manipulation of rubber band boxes
107    */
108    public class MyRubberBandBoxRendererJ3D extends DirectManipulationRendererJ3D {
109    
110      private RealType x = null;
111      private RealType y = null;
112      private RealTupleType xy = null;
113    
114      private int mouseModifiersMask  = 0;
115      private int mouseModifiersValue = 0;
116    
117      private BranchGroup branch = null;
118      private BranchGroup group  = null;
119      //- TDR
120      private boolean       keep_last_box   = false;
121      private BranchGroup   last_group      = null;
122      private GeometryArray last_geometry   = null;
123      private Appearance    last_appearance = null;
124      public Gridded3DSet   last_box        = null;
125    
126      public  boolean enabled = true;
127      public  boolean active  = true;
128    
129      /** this DirectManipulationRenderer is quite different - it does not
130          render its data, but only place values into its DataReference
131          on right mouse button release;
132          it uses xarg and yarg to determine spatial ScalarMaps */
133      public MyRubberBandBoxRendererJ3D (RealType xarg, RealType yarg) {
134        this(xarg, yarg, 0, 0);
135      }
136    
137      /** xarg and yarg determine spatial ScalarMaps;
138          mmm and mmv determine whehter SHIFT or CTRL keys are required -
139          this is needed since this is a greedy DirectManipulationRenderer
140          that will grab any right mouse click (that intersects its 2-D
141          sub-manifold) */
142      public MyRubberBandBoxRendererJ3D (RealType xarg, RealType yarg, int mmm, int mmv) {
143        super();
144        x = xarg;
145        y = yarg;
146        mouseModifiersMask = mmm;
147        mouseModifiersValue = mmv;
148      }
149    
150      /** don't render - just return BranchGroup for scene graph to
151          render rectangle into */
152      public synchronized BranchGroup doTransform()
153             throws VisADException, RemoteException {
154        branch = new BranchGroup();
155        branch.setCapability(BranchGroup.ALLOW_DETACH);
156        branch.setCapability(Group.ALLOW_CHILDREN_READ);
157        branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
158        branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
159    
160        // check type and maps for valid direct manipulation
161        if (!getIsDirectManipulation()) {
162          throw new BadDirectManipulationException(getWhyNotDirect() +
163            ": DirectManipulationRendererJ3D.doTransform");
164        }
165        setBranch(branch);
166    
167        if (keep_last_box) { //-TDR
168          if (last_group != null) last_group.detach();
169          branch.addChild(last_group);
170        }
171    
172        return branch;
173      }
174    
175      /** for use in drag_direct */
176      private transient DataDisplayLink link = null;
177      private transient DataReference ref = null;
178    
179      private transient ScalarMap xmap = null;
180      private transient ScalarMap ymap = null;
181    
182      float[] default_values;
183    
184      /** arrays of length one for inverseScaleValues */
185      private float[] f = new float[1];
186      private float[] d = new float[1];
187      private float[] value = new float[2];
188    
189      /** information calculated by checkDirect */
190      /** explanation for invalid use of DirectManipulationRenderer */
191      private String whyNotDirect = null;
192      /** dimension of direct manipulation
193          (always 2 for RubberBandBoxRendererJ3D) */
194      private int directManifoldDimension = 2;
195      /** spatial DisplayTupleType other than
196          DisplaySpatialCartesianTuple */
197      private DisplayTupleType tuple;
198      private CoordinateSystem tuplecs;
199    
200      private int xindex = -1;
201      private int yindex = -1;
202      private int otherindex = -1;
203      private float othervalue;
204    
205      private byte red, green, blue; // default colors
206    
207      private float[][] first_x;
208      private float[][] last_x;
209      private float[][] clast_x;
210      private float cum_lon;
211    
212      /** possible values for whyNotDirect */
213      private final static String xandyNotMatch =
214        "x and y spatial domains don't match";
215      private final static String xandyNotSpatial =
216        "x and y must be mapped to spatial";
217    
218    
219      private boolean stop = false;
220    
221      public void checkDirect() throws VisADException, RemoteException {
222        setIsDirectManipulation(false);
223    
224        DisplayImpl display = getDisplay();
225    
226        DataDisplayLink[] Links = getLinks();
227        if (Links == null || Links.length == 0) {
228          link = null;
229          return;
230        }
231        link = Links[0];
232    
233        ref = link.getDataReference();
234        default_values = link.getDefaultValues();
235    
236        xmap = null;
237        ymap = null;
238        Vector scalar_map_vector = display.getMapVector();
239        Enumeration smaps = scalar_map_vector.elements();
240        while (smaps.hasMoreElements()) {
241          ScalarMap map = (ScalarMap) smaps.nextElement();
242          ScalarType real = map.getScalar();
243          if (real.equals(x)) {
244            DisplayRealType dreal = map.getDisplayScalar();
245            DisplayTupleType t = dreal.getTuple();
246            if (t != null &&
247                (t.equals(Display.DisplaySpatialCartesianTuple) ||
248                 (t.getCoordinateSystem() != null &&
249                  t.getCoordinateSystem().getReference().equals(
250                  Display.DisplaySpatialCartesianTuple)))) {
251              xmap = map;
252              xindex = dreal.getTupleIndex();
253              if (tuple == null) {
254                tuple = t;
255              }
256              else if (!t.equals(tuple)) {
257                whyNotDirect = xandyNotMatch;
258                return;
259              }
260            }
261          }
262          if (real.equals(y)) {
263            DisplayRealType dreal = map.getDisplayScalar();
264            DisplayTupleType t = dreal.getTuple();
265            if (t != null &&
266                (t.equals(Display.DisplaySpatialCartesianTuple) ||
267                 (t.getCoordinateSystem() != null &&
268                  t.getCoordinateSystem().getReference().equals(
269                  Display.DisplaySpatialCartesianTuple)))) {
270              ymap = map;
271              yindex = dreal.getTupleIndex();
272              if (tuple == null) {
273                tuple = t;
274              }
275              else if (!t.equals(tuple)) {
276                whyNotDirect = xandyNotMatch;
277                return;
278              }
279            }
280          }
281        }
282    
283        if (xmap == null || ymap == null) {
284          whyNotDirect = xandyNotSpatial;
285          return;
286        }
287    
288        xy = new RealTupleType(x, y);
289    
290        // get default value for other component of tuple
291        otherindex = 3 - (xindex + yindex);
292        DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
293        int index = getDisplay().getDisplayScalarIndex(dreal);
294        othervalue = (index > 0) ? default_values[index] :
295                                   (float) dreal.getDefaultValue();
296    
297        // get default colors
298        index = getDisplay().getDisplayScalarIndex(Display.Red);
299        float v = (index > 0) ? default_values[index] :
300                               (float) Display.Red.getDefaultValue();
301        red = ShadowType.floatToByte(v);
302        index = getDisplay().getDisplayScalarIndex(Display.Green);
303        v = (index > 0) ? default_values[index] :
304                          (float) Display.Green.getDefaultValue();
305        green = ShadowType.floatToByte(v);
306        index = getDisplay().getDisplayScalarIndex(Display.Blue);
307        v = (index > 0) ? default_values[index] :
308                          (float) Display.Blue.getDefaultValue();
309        blue = ShadowType.floatToByte(v);
310    
311        if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
312          tuple = null;
313          tuplecs = null;
314        }
315        else {
316          tuplecs = tuple.getCoordinateSystem();
317        }
318    
319        directManifoldDimension = 2;
320        setIsDirectManipulation(true);
321      }
322    
323      private int getDirectManifoldDimension() {
324        return directManifoldDimension;
325      }
326    
327      public String getWhyNotDirect() {
328        return whyNotDirect;
329      }
330    
331      public void addPoint(float[] x) throws VisADException {
332        // may need to do this for performance
333      }
334    
335    // methods customized from DataRenderer:
336    
337      public CoordinateSystem getDisplayCoordinateSystem() {
338        return tuplecs;
339      }
340    
341      /** set spatialValues from ShadowType.doTransform */
342      public synchronized void setSpatialValues(float[][] spatial_values) {
343        // do nothing
344      }
345    
346      /** check if ray intersects sub-manifold */
347      public synchronized float checkClose(double[] origin, double[] direction) {
348        if (!enabled) return Float.MAX_VALUE;
349        if (!active) {
350          return Float.MAX_VALUE;
351        }
352        int mouseModifiers = getLastMouseModifiers();
353        if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
354          return Float.MAX_VALUE;
355        }
356    
357        try {
358          float r = findRayManifoldIntersection(true, origin, direction, tuple,
359                                                otherindex, othervalue);
360          if (r == r) {
361            // force pick close strategy: if close enough to another manipulation renderer
362            return getDisplayRenderer().getPickThreshhold() - 0.005f;
363          }
364          else {
365            return Float.MAX_VALUE;
366          }
367        }
368        catch (VisADException ex) {
369          return Float.MAX_VALUE;
370        }
371      }
372    
373      /** mouse button released, ending direct manipulation */
374      public synchronized void release_direct() {
375        // set data in ref
376        if (!enabled) return;
377        if (group != null) group.detach();
378        group = null;
379        try {
380          float[][] samples = new float[2][2];
381          f[0] = first_x[xindex][0];
382          d = xmap.inverseScaleValues(f);
383          d[0] = f[0];
384          samples[0][0] = (float) d[0];
385          f[0] = first_x[yindex][0];
386          d = ymap.inverseScaleValues(f);
387          d[0] = f[0];
388          samples[1][0] = (float) d[0];
389          f[0] = last_x[xindex][0];
390          d = xmap.inverseScaleValues(f);
391          d[0] = f[0];
392          samples[0][1] = (float) d[0];
393          f[0] = last_x[yindex][0];
394          d = ymap.inverseScaleValues(f);
395          d[0] = f[0];
396          samples[1][1] = (float) d[0];
397          Gridded2DSet set = new Gridded2DSet(xy, samples, 2);
398          ref.setData(set);
399          link.clearData();
400        } // end try
401        catch (VisADException e) {
402          // do nothing
403          System.out.println("release_direct " + e);
404          e.printStackTrace();
405        }
406        catch (RemoteException e) {
407          // do nothing
408          System.out.println("release_direct " + e);
409          e.printStackTrace();
410        }
411      }
412    
413      public void stop_direct() {
414        stop = true;
415      }
416    
417      private static final int EDGE = 20;
418    
419      private static final float EPS = 0.005f;
420    
421      public synchronized void drag_direct(VisADRay ray, boolean first,
422                                           int mouseModifiers) {
423        if (ref == null) return;
424        if (enabled == false) return;
425    
426        if (first) {
427          stop = false;
428        }
429        else {
430          if (stop) return;
431        }
432    
433        double[] origin = ray.position;
434        double[] direction = ray.vector;
435    
436        try {
437          float r = findRayManifoldIntersection(true, origin, direction, tuple,
438                                                otherindex, othervalue);
439          if (r != r) {
440            if (group != null) group.detach();
441            return;
442          }
443          float[][] xx = {{(float) (origin[0] + r * direction[0])},
444                          {(float) (origin[1] + r * direction[1])},
445                          {(float) (origin[2] + r * direction[2])}};
446          if (tuple != null) xx = tuplecs.fromReference(xx);
447    
448          if (first) {
449            first_x = xx;
450            cum_lon = 0.0f;
451          }
452          else if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
453            float diff = xx[1][0] - clast_x[1][0];
454            if (diff > 180.0f) diff -= 360.0f;
455            else if (diff < -180.0f) diff += 360.0f;
456            cum_lon += diff;
457            if (cum_lon > 360.0f) cum_lon -= 360.0f;
458            else if (cum_lon < -360.0f) cum_lon += 360.0f;
459          }
460          clast_x = xx;
461    
462          Vector vect = new Vector();
463          f[0] = xx[xindex][0];
464          d = xmap.inverseScaleValues(f);
465    
466          // WLH 31 Aug 2000
467          Real rr = new Real(x, d[0]);
468          Unit overrideUnit = xmap.getOverrideUnit();
469          Unit rtunit = x.getDefaultUnit();
470          // units not part of Time string
471          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
472              !RealType.Time.equals(x)) {
473            double dval =  overrideUnit.toThis((double) d[0], rtunit);
474            rr = new Real(x, dval, overrideUnit);
475          }   
476          String valueString = rr.toValueString();
477    
478          vect.addElement(x.getName() + " = " + valueString);
479          f[0] = xx[yindex][0];
480          d = ymap.inverseScaleValues(f);
481    
482          // WLH 31 Aug 2000
483          rr = new Real(y, d[0]);
484          overrideUnit = ymap.getOverrideUnit();
485          rtunit = y.getDefaultUnit();
486          // units not part of Time string
487          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
488              !RealType.Time.equals(y)) {
489            double dval =  overrideUnit.toThis((double) d[0], rtunit);
490            rr = new Real(y, dval, overrideUnit);
491          }
492          valueString = rr.toValueString();
493    
494          valueString = new Real(y, d[0]).toValueString();
495          vect.addElement(y.getName() + " = " + valueString);
496          getDisplayRenderer().setCursorStringVector(vect);
497    
498          float[][] xxp = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
499          xxp[otherindex][0] += EPS;
500          if (tuplecs != null) xxp = tuplecs.toReference(xxp);
501          float[][] xxm = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
502          xxm[otherindex][0] -= EPS;
503          if (tuplecs != null) xxm = tuplecs.toReference(xxm);
504          double dot = (xxp[0][0] - xxm[0][0]) * direction[0] +
505                       (xxp[1][0] - xxm[1][0]) * direction[1] +
506                       (xxp[2][0] - xxm[2][0]) * direction[2];
507          float abs = (float)
508            Math.sqrt((xxp[0][0] - xxm[0][0]) * (xxp[0][0] - xxm[0][0]) +
509                     (xxp[1][0] - xxm[1][0]) * (xxp[1][0] - xxm[1][0]) +
510                     (xxp[2][0] - xxm[2][0]) * (xxp[2][0] - xxm[2][0]));
511          float other_offset = EPS * (2.0f * EPS / abs);
512          if (dot >= 0.0) other_offset = -other_offset;
513    
514          last_x =
515            new float[][] {{clast_x[0][0]}, {clast_x[1][0]}, {clast_x[2][0]}};
516          if (Display.DisplaySpatialSphericalTuple.equals(tuple) &&
517              otherindex != 1) {
518            if (last_x[1][0] < first_x[1][0] && cum_lon > 0.0f) {
519              last_x[1][0] += 360.0;
520            }
521            else if (last_x[1][0] > first_x[1][0] && cum_lon < 0.0f) {
522              last_x[1][0] -= 360.0;
523            }
524          }
525    
526          int npoints = 4 * EDGE + 1;
527          float[][] c = new float[3][npoints];
528          for (int i=0; i<EDGE; i++) {
529            float a = ((float) i) / EDGE;
530            float b = 1.0f - a;
531            c[xindex][i] = b * first_x[xindex][0] + a * last_x[xindex][0];
532            c[yindex][i] = first_x[yindex][0];
533            c[otherindex][i] = first_x[otherindex][0] + other_offset;
534            c[xindex][EDGE + i] = last_x[xindex][0];
535            c[yindex][EDGE + i] = b * first_x[yindex][0] + a * last_x[yindex][0];
536            c[otherindex][EDGE + i] = first_x[otherindex][0] + other_offset;
537            c[xindex][2 * EDGE + i] = b * last_x[xindex][0] + a * first_x[xindex][0];
538            c[yindex][2 * EDGE + i] = last_x[yindex][0];
539            c[otherindex][2 * EDGE + i] = first_x[otherindex][0] + other_offset;
540            c[xindex][3 * EDGE + i] = first_x[xindex][0];
541            c[yindex][3 * EDGE + i] = b * last_x[yindex][0] + a * first_x[yindex][0];
542            c[otherindex][3 * EDGE + i] = first_x[otherindex][0] + other_offset;
543          }
544          c[0][npoints - 1] = c[0][0];
545          c[1][npoints - 1] = c[1][0];
546          c[2][npoints - 1] = c[2][0];
547          if (tuple != null) c = tuplecs.toReference(c);
548          float[] coordinates = new float[3 * npoints];
549          last_box = new Gridded3DSet(RealTupleType.SpatialCartesian3DTuple, c, npoints);
550          for (int i=0; i<npoints; i++) {
551            int i3 = 3 * i;
552            coordinates[i3] = c[0][i];
553            coordinates[i3 + 1] = c[1][i];
554            coordinates[i3 + 2] = c[2][i];
555          }
556          VisADLineStripArray array = new VisADLineStripArray();
557          array.vertexCount = npoints;
558          array.stripVertexCounts = new int[1];
559          array.stripVertexCounts[0] = npoints;
560          array.coordinates = coordinates;
561          byte[] colors = new byte[3 * npoints];
562          for (int i=0; i<npoints; i++) {
563            int i3 = 3 * i;
564            colors[i3] = red;
565            colors[i3 + 1] = green;
566            colors[i3 + 2] = blue;
567          }
568          array.colors = colors;
569          /** TDR skip this: can be problems with lon/lat point CS's, and need
570              to be able to draw box over dateline and GM (TODO).
571          array = (VisADLineStripArray) array.adjustSeam(this);
572          */
573    
574          DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
575          GeometryArray geometry = display.makeGeometry(array);
576      
577          DataDisplayLink[] Links = getLinks();
578          if (Links == null || Links.length == 0) {
579            return;
580          }
581          DataDisplayLink link = Links[0];
582    
583          float[] default_values = link.getDefaultValues();
584          GraphicsModeControl mode = (GraphicsModeControl)
585            display.getGraphicsModeControl().clone();
586          float pointSize =
587            default_values[display.getDisplayScalarIndex(Display.PointSize)];
588          float lineWidth =
589            default_values[display.getDisplayScalarIndex(Display.LineWidth)];
590          mode.setPointSize(pointSize, true);
591          mode.setLineWidth(lineWidth, true);
592          Appearance appearance =
593            ShadowTypeJ3D.staticMakeAppearance(mode, null, null, geometry, false);
594    
595          if (group != null) group.detach();
596          group = null;
597    
598          Shape3D shape = new Shape3D(geometry, appearance);
599          group = new BranchGroup();
600          group.setCapability(Group.ALLOW_CHILDREN_READ);
601          group.setCapability(BranchGroup.ALLOW_DETACH);
602          group.addChild(shape);
603    
604          //-- TDR
605          if (keep_last_box) {
606            last_group      = group;
607            last_geometry   = geometry;
608            last_appearance = appearance;
609          }
610    
611          if (branch != null) branch.addChild(group);
612        } // end try
613        catch (VisADException e) {
614          // do nothing
615          e.printStackTrace();
616        }
617      }
618    
619      public Object clone() {
620        return new MyRubberBandBoxRendererJ3D(x, y, mouseModifiersMask,
621                                            mouseModifiersValue);
622      }
623    
624    
625      //---------------------------------------------------------
626      public void setKeepLastBoxOn(boolean keep) {
627        //- default is false
628        keep_last_box = keep;
629      }
630    
631      public void removeLastBox() {
632        if (last_group != null) {
633          last_group.detach();
634        }
635      }
636    
637      public BranchGroup getLastBox() {
638        Shape3D shape     = new Shape3D(last_geometry, last_appearance);
639        BranchGroup group = new BranchGroup();
640        group.setCapability(Group.ALLOW_CHILDREN_READ);
641        group.setCapability(BranchGroup.ALLOW_DETACH);
642        group.addChild(shape);
643        return group;
644      }
645    
646      public void setLastBox(BranchGroup box_bg) {
647        if (last_group != null) {
648          last_group.detach();
649        }
650        last_group = box_bg;
651        branch.addChild(box_bg);
652      }
653    
654      public void setLastBox(MyRubberBandBoxRendererJ3D rbbr) {
655        BranchGroup box_bg = rbbr.getLastBox();
656        if (last_group != null) {
657          last_group.detach();
658        }
659        last_group = box_bg;
660        branch.addChild(box_bg);
661      }
662      //-----------------------------------------------------------
663    
664    }
665