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