001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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// 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