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