001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029//
030// GrabLineRendererJ3D.java
031//
032
033package edu.wisc.ssec.mcidasv.data.hydra;
034
035import java.rmi.RemoteException;
036import java.util.Enumeration;
037import java.util.Vector;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041import visad.CommonUnit;
042import visad.Data;
043import visad.DataDisplayLink;
044import visad.DataReference;
045import visad.Display;
046import visad.DisplayImpl;
047import visad.DisplayRealType;
048import visad.DisplayTupleType;
049import visad.Gridded1DSet;
050import visad.MathType;
051import visad.Real;
052import visad.RealTuple;
053import visad.RealTupleType;
054import visad.RealType;
055import visad.ScalarMap;
056import visad.ShadowRealType;
057import visad.ShadowType;
058import visad.Unit;
059import visad.VisADException;
060import visad.VisADRay;
061
062/**
063 Grab and drag lines parallel to a coordinate axis.  For simple
064 2D graphs, not yet generalized for 3D displays.  For a 
065 vertical line, map Real to Display.XAxis, and assign 
066 ConstantMap for Display.YAxis.  Vice-a-versa for a horizontal
067 line.
068 */
069
070public class GrabLineRendererJ3D extends visad.java3d.DirectManipulationRendererJ3D {
071  
072  private static final Logger logger =
073      LoggerFactory.getLogger(GrabLineRendererJ3D.class);
074  
075  private float[][] spatialValues = null;
076
077  private int closeIndex = -1;
078
079  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
080  private int offset_count = 0;
081  private static final int OFFSET_COUNT_INIT = 30;
082
083  private transient DataDisplayLink link = null;
084  private transient DataReference ref = null;
085  private transient MathType type = null;
086  private transient ShadowType shadow = null;
087
088  private float point_x, point_y, point_z;
089  private float line_x, line_y, line_z;
090
091  private float[] f = new float[1];
092  private float[] d = new float[1];
093
094  private String notRealType = "not RealType";
095  private String whyNotDirect = null;
096
097  private boolean pickCrawlToCursor = true;
098
099  private int[] axisToComponent = {-1, -1, -1};
100  private ScalarMap[] directMap = {null, null, null};
101
102  private DisplayImpl display = null;
103  private DisplayTupleType tuple = null;
104  private boolean stop = false;
105
106  private Gridded1DSet domainSet = null;
107  private int last_idx = -1;
108  private float[][] samples = null;
109
110  private int mouseModifiersMask  = 0;
111  private int mouseModifiersValue = 0;
112
113  public GrabLineRendererJ3D() {
114    this(null);
115  }
116
117  public GrabLineRendererJ3D(Gridded1DSet domainSet) {
118    super();
119    this.domainSet = domainSet;
120    try {
121      if (domainSet != null) samples = domainSet.getSamples();
122    }
123    catch (Exception e) {
124      System.out.println(e.getMessage());
125    }
126  }
127
128  public String getWhyNotDirect() {
129    return whyNotDirect;
130  }
131
132  public synchronized void setSpatialValues(float[][] spatial_values) {
133    spatialValues = spatial_values;
134  }
135
136  public void checkDirect() throws VisADException, RemoteException {
137    setIsDirectManipulation(false);
138
139    display = getDisplay();
140    link = getLinks()[0];
141    ref = link.getDataReference();
142    shadow = link.getShadow().getAdaptedShadowType();
143    type = link.getType();
144    if (!(type instanceof RealType)) {
145      whyNotDirect = notRealType;
146      return;
147    }
148
149    tuple = ((ShadowRealType) shadow).getDisplaySpatialTuple();
150
151    //-ShadowRealType[] components = shadow.getRealComponents();
152    ShadowRealType[] components = {(ShadowRealType)shadow};
153
154    for (int i=0; i<components.length; i++) {
155      Enumeration maps = components[i].getSelectedMapVector().elements();
156      while (maps.hasMoreElements()) {
157        ScalarMap map = (ScalarMap) maps.nextElement();
158        DisplayRealType dreal = map.getDisplayScalar();
159        DisplayTupleType tuple = dreal.getTuple();
160        if (tuple != null &&
161            (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
162             (tuple.getCoordinateSystem() != null &&
163              tuple.getCoordinateSystem().getReference().equals(
164              Display.DisplaySpatialCartesianTuple)))) {
165          int index = dreal.getTupleIndex();
166          axisToComponent[index] = i;
167          directMap[index] = map;
168        }
169      } // end while (maps.hasMoreElements())
170    }
171
172    setIsDirectManipulation(true);
173  }
174
175
176  public synchronized float checkClose(double[] origin, double[] direction) 
177  {
178    int mouseModifiers = getLastMouseModifiers();
179    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
180      return Float.MAX_VALUE;
181    }
182
183    float distance = Float.MAX_VALUE;
184    if (display == null) return distance;
185    if (spatialValues == null) return distance;
186    float o_x = (float) origin[0];
187    float o_y = (float) origin[1];
188    float o_z = (float) origin[2];
189    float d_x = (float) direction[0];
190    float d_y = (float) direction[1];
191    float d_z = (float) direction[2];
192/*
193System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
194System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
195*/
196
197    for (int i=0; i<spatialValues[0].length; i++) {
198      float x = spatialValues[0][i] - o_x;
199      float y = spatialValues[1][i] - o_y;
200      float z = spatialValues[2][i] - o_z;
201      float dot = x * d_x + y * d_y + z * d_z;
202      x = x - dot * d_x;
203      y = y - dot * d_y;
204      z = z - dot * d_z;
205      float d = (float) Math.sqrt(x * x + y * y + z * z);
206      if (d < distance) {
207        distance = d;
208        closeIndex = i;
209        offsetx = x;
210        offsety = y;
211        offsetz = z;
212      }
213/*
214System.out.println("spatialValues["+i+"] = " + spatialValues[0][i] + " " +
215spatialValues[1][i] + " " + spatialValues[2][i] + " d = " + d);
216*/
217    }
218
219    float dist1D = Float.MAX_VALUE;
220    if (axisToComponent[0] != -1) dist1D = offsetx;
221    if (axisToComponent[1] != -1) dist1D = offsety;
222    if (axisToComponent[2] != -1) dist1D = offsetz;
223    return Math.abs(dist1D);
224  }
225
226  public synchronized void drag_direct(VisADRay ray, boolean first,
227                                       int mouseModifiers) {
228    if (display == null) return;
229    
230    // disable printing of the cursor info string
231    getDisplayRenderer().setCursorStringOn(false);
232    
233    // System.out.println("drag_direct " + first + " " + type);
234    if (spatialValues == null || ref == null || shadow == null ||
235        link == null) return;
236
237    if (first) {
238      stop = false;
239    }
240    else {
241      if (stop) return;
242    }
243
244    float o_x = (float) ray.position[0];
245    float o_y = (float) ray.position[1];
246    float o_z = (float) ray.position[2];
247    float d_x = (float) ray.vector[0];
248    float d_y = (float) ray.vector[1];
249    float d_z = (float) ray.vector[2];
250
251    if (pickCrawlToCursor) {
252      if (first) {
253        offset_count = OFFSET_COUNT_INIT;
254      }
255      else {
256        if (offset_count > 0) offset_count--;
257      }
258      if (offset_count > 0) {
259        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
260        o_x += mult * offsetx;
261        o_y += mult * offsety;
262        o_z += mult * offsetz;
263      }
264    }
265
266    if (first) {
267      point_x = spatialValues[0][closeIndex];
268      point_y = spatialValues[1][closeIndex];
269      point_z = spatialValues[2][closeIndex];
270      int lineAxis = -1;
271      for (int i=0; i<3; i++) {
272        if (getAxisToComponent(i) >= 0) {
273          lineAxis = i;
274        }
275      }
276      line_x = (lineAxis == 0) ? 1.0f : 0.0f;
277      line_y = (lineAxis == 1) ? 1.0f : 0.0f;
278      line_z = (lineAxis == 2) ? 1.0f : 0.0f;
279    }
280    float[] x = new float[3];
281
282    // find closest point on line to ray
283    // logic from vis5d/cursor.c
284    // line o_, d_ to line point_, line_
285    float ld = d_x * line_x + d_y * line_y + d_z * line_z;
286    float od = o_x * d_x + o_y * d_y + o_z * d_z;
287    float pd = point_x * d_x + point_y * d_y + point_z * d_z;
288    float ol = o_x * line_x + o_y * line_y + o_z * line_z;
289    float pl = point_x * line_x + point_y * line_y + point_z * line_z;
290    if (ld * ld == 1.0f) return;
291    float t = ((pl - ol) - (ld * (pd - od))) / (ld * ld - 1.0f);
292    // x is closest point
293    x[0] = point_x + t * line_x;
294    x[1] = point_y + t * line_y;
295    x[2] = point_z + t * line_z;
296
297    try {
298      float[] xx = {x[0], x[1], x[2]};
299      if (tuple != null) {
300        /*- TDR ??
301        float[][] cursor = {{x[0]}, {x[1]}, {x[2]}};
302        float[][] new_cursor =
303          tuple.getCoordinateSystem().fromReference(cursor);
304        x[0] = new_cursor[0][0];
305        x[1] = new_cursor[1][0];
306        x[2] = new_cursor[2][0];
307        */
308      }
309      Data newData = null;
310      Data data;
311      try {
312        data = link.getData();
313      } catch (RemoteException re) {
314        if (visad.collab.CollabUtil.isDisconnectException(re)) {
315          getDisplay().connectionFailed(this, link);
316          removeLink(link);
317          link = null;
318          return;
319        }
320        throw re;
321      }
322      int ii = -1;
323      RealType rtype = null;
324      if (type instanceof RealType) {
325        if (domainSet == null) addPoint(xx);
326        for (int i=0; i<3; i++) {
327          if (getAxisToComponent(i) >= 0) {
328            ii = i;
329            f[0] = x[i];
330            d = getDirectMap(i).inverseScaleValues(f);
331            // RealType rtype = (RealType) data.getType();
332            rtype = (RealType) type;
333            newData = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
334            break;
335          }
336        }
337        if (domainSet != null) {
338          int[] idx = domainSet.valueToIndex(new float[][] {d});
339          if (idx[0] != last_idx && idx[0] >= 0) {
340            newData = new Real(rtype, (double)samples[0][idx[0]], rtype.getDefaultUnit(), null);
341
342            // create location string
343            Vector<String> vect = new Vector<String>();
344            //-Real r = new Real(rtype, d[0]);
345            Real r = new Real(rtype, samples[0][idx[0]]);
346            Unit overrideUnit = getDirectMap(ii).getOverrideUnit();
347            Unit rtunit = rtype.getDefaultUnit();
348            // units not part of Time string
349            if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
350                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
351                 rtunit.getAbsoluteUnit().equals(rtunit))) {
352              double dval =  overrideUnit.toThis((double) d[0], rtunit);
353              r = new Real(rtype, dval, overrideUnit);
354            }
355            String valueString = r.toValueString();
356            vect.addElement(rtype.getName() + " = " + valueString);
357            getDisplayRenderer().setCursorStringVector(vect);
358
359            ref.setData(newData);
360            link.clearData();
361            last_idx = idx[0];
362          }
363        }
364        else {
365          ref.setData(newData);
366          link.clearData();
367        }
368      }
369      else if (type instanceof RealTupleType) {
370        addPoint(xx);
371        int n = ((RealTuple) data).getDimension();
372        Real[] reals = new Real[n];
373        Vector<String> vect = new Vector<String>();
374        for (int i=0; i<3; i++) {
375          int j = getAxisToComponent(i);
376          if (j >= 0) {
377            f[0] = x[i];
378            d = getDirectMap(i).inverseScaleValues(f);
379            Real c = (Real) ((RealTuple) data).getComponent(j);
380            rtype = (RealType) c.getType();
381            reals[j] = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
382          }
383        }
384        getDisplayRenderer().setCursorStringVector(vect);
385        for (int j=0; j<n; j++) {
386          if (reals[j] == null) {
387            reals[j] = (Real) ((RealTuple) data).getComponent(j);
388          }
389        }
390        newData = new RealTuple((RealTupleType) type, reals,
391                                ((RealTuple) data).getCoordinateSystem());
392        //ref.setData(newData);
393        //link.clearData();
394
395        if (domainSet != null) {
396          int[] idx = domainSet.valueToIndex(new float[][] {d});
397          if (idx[0] != last_idx && idx[0] >= 0) {
398            newData = new Real(rtype, (double)samples[0][idx[0]], rtype.getDefaultUnit(), null);
399                                                                                                                  
400            // create location string
401            vect = new Vector<String>();
402            //-Real r = new Real(rtype, d[0]);
403            Real r = new Real(rtype, samples[0][idx[0]]);
404            Unit overrideUnit = getDirectMap(ii).getOverrideUnit();
405            Unit rtunit = rtype.getDefaultUnit();
406            // units not part of Time string
407            if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
408                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
409                 rtunit.getAbsoluteUnit().equals(rtunit))) {
410              double dval =  overrideUnit.toThis((double) d[0], rtunit);
411              r = new Real(rtype, dval, overrideUnit);
412            }
413            String valueString = r.toValueString();
414            vect.addElement(rtype.getName() + " = " + valueString);
415            getDisplayRenderer().setCursorStringVector(vect);
416            
417            ref.setData(newData);
418            link.clearData();
419            last_idx = idx[0];
420          }
421        }
422        else {
423          ref.setData(newData);
424          link.clearData();
425        }
426
427      }
428
429   } catch (VisADException | RemoteException e) {
430     logger.error("Problem in drag_direct", e);
431   }
432  }
433
434  private int getAxisToComponent(int i) {
435    return axisToComponent[i];
436  }
437
438  private ScalarMap getDirectMap(int i) {
439    return directMap[i];
440  }
441
442  public void stop_direct() {
443    stop = true;
444  }
445}