//
// GridEdit.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.bom;
import visad.*;
import visad.util.*;
import visad.java3d.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;
import java.util.Enumeration;
import java.rmi.*;
/**
</pre>
GridEdit is the VisAD class for warping and modifying fields.<p>
Construct a GridEdit object linked to a 2-D grid [a FlatField
with MathType ((x, y) -> range)]) or a sequence of 2-D grids [a
FieldImpl with MathType (t -> ((x, y) -> range))], and a DisplayImpl.
The grid or grids must all have the same domain Set, which must be a
Gridded2DSet or a GriddedSet with domain dimension = 2.
The domain must be mapped to two spatial DisplayRealTypes. If a
sequence of grids, the sequence domain must be mapped to Animation.
The grid may have any number of range RealTypes.
The GridEdit object operates in a sequence:
1. Invokes its start() method to start.
2. User drags grid warp motion lines with the right mouse button.
These lines must lie inside the grid.
3. If user presses SHIFT while dragging a grid warp motion line, the
program prompts for increments for values at the dragged location.
4. User can delete lines by clicking the right button on their end
points, with CTRL pressed.
5. At any point after start(), the application can invoke stop() to
stop the dragging process, and warp the grid.
6. After the grid has been warped, the application can invoke undo()
to undo the paste and stop the process.
7. The process can be restarted by invoking start(), any number of times.
The main() method illustrates a simple GUI and test case with a sequnece
of grids. Run 'java visad.bom.GridEdit' to test with contour
values, and run 'java visad.bom.GridEdit 1' to test with color
values.
</pre>
*/
public class GridEdit extends Object implements ActionListener {
private boolean debug = false;
private Field grids = null;
private DisplayImpl display = null;
private Object lock = new Object();
private RealType t = null; // non-null if animation
private RealType x = null;
private RealType y = null;
private RealTupleType xy = null;
private MathType range = null; // RealType or RealTupleType
private int rangedim = 0;
private int nts = 0; // number of steps in sequence
Set tset = null; // t domain Set
GriddedSet xyset = null; // (x, y) domain Set
FunctionType grid_type;
FlatField replacedff = null;
FlatField savedff = null;
AnimationControl acontrol = null;
ScalarMap tmap = null;
ScalarMap xmap = null;
ScalarMap ymap = null;
DisplayTupleType xtuple = null;
DisplayTupleType ytuple = null;
private CellImpl cell_rbl = null;
private DataReferenceImpl ref_rbl = null;
private RubberBandLineRendererJ3D rblr = null;
private final static int NPICKS = 50;
private int npicks = 0;
private PickCell[] cell_picks = null;
private DataReferenceImpl[] ref_picks = null;
private PickManipulationRendererJ3D[] rend_picks = null;
private float[][] delta_picks = null;
private GridEdit thiscp = null;
private int renderer_mask;
private int dialog_mask;
/**
<pre>
gs has MathType (t -> ((x, y) -> v)) or ((x, y) -> v)
conditions on gs and display:
1. x and y mapped to XAxis, YAxis, ZAxis
2. (x, y) domain GriddedSet
3. if (t -> ...), then t is mapped to Animation
</pre>
*/
public GridEdit(Field gs, DisplayImplJ3D d)
throws VisADException, RemoteException {
grids = gs;
display = d;
renderer_mask = InputEvent.CTRL_MASK;
dialog_mask = InputEvent.SHIFT_MASK;
thiscp = this;
FunctionType gstype = (FunctionType) gs.getType();
RealTupleType domain = gstype.getDomain();
int domdim = domain.getDimension();
if (domdim == 1) {
t = (RealType) domain.getComponent(0);
tset = gs.getDomainSet();
grid_type = (FunctionType) gstype.getRange();
xy = grid_type.getDomain();
int dim = xy.getDimension();
if (dim != 2) {
throw new VisADException("bad grid Field domain dimension: " + dim);
}
range = grid_type.getRange();
nts = tset.getLength();
for (int i=0; i<nts; i++) {
FlatField ff = (FlatField) gs.getSample(i);
Set s = ff.getDomainSet();
if (!(s instanceof GriddedSet)) {
throw new VisADException("grid set must be GriddedSet");
}
if (xyset == null) {
xyset = (GriddedSet) s;
}
else {
if (!xyset.equals(s)) {
throw new VisADException("grid sets must match in animation");
}
}
}
}
else if (domdim == 2) {
t = null;
tset = null;
xy = domain;
grid_type = gstype;
range = gstype.getRange();
Set s = gs.getDomainSet();
if (!(s instanceof GriddedSet)) {
throw new VisADException("grid set must be GriddedSet");
}
xyset = (GriddedSet) s;
}
else {
throw new VisADException("bad grid Field domain dimension: " + domdim);
}
x = (RealType) xy.getComponent(0);
y = (RealType) xy.getComponent(1);
if (range instanceof RealType) {
rangedim = 1;
}
else if (range instanceof RealTupleType) {
rangedim = ((RealTupleType) range).getDimension();
}
else {
throw new VisADException("bad grid Field range type: " + range);
}
Vector scalar_map_vector = display.getMapVector();
Enumeration en = scalar_map_vector.elements();
while (en.hasMoreElements()) {
ScalarMap map = (ScalarMap) en.nextElement();
ScalarType scalar = map.getScalar();
DisplayRealType dreal = map.getDisplayScalar();
DisplayTupleType tuple = dreal.getTuple();
if (scalar.equals(t)) {
if (Display.Animation.equals(dreal)) {
tmap = map;
acontrol = (AnimationControl) tmap.getControl();
}
}
else if (tuple != null &&
(tuple.equals(Display.DisplaySpatialCartesianTuple) ||
(tuple.getCoordinateSystem() != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)))) { // spatial
if (scalar.equals(x)) {
xmap = map;
xtuple = tuple;
}
else if (scalar.equals(y)) {
ymap = map;
ytuple = tuple;
}
}
}
if (xmap == null || ymap == null || xtuple != ytuple) {
throw new VisADException("grid domain RealTypes must be mapped to " +
"spatial DisplayRealTypes from the same DisplayTupleType");
}
if (t != null && tmap == null) {
throw new VisADException("grid sequence must be mapped to Animation");
}
makePicks(NPICKS);
ref_rbl = new DataReferenceImpl("rbl");
rblr = new RubberBandLineRendererJ3D(x, y, renderer_mask, 0);
display.addReferences(rblr, ref_rbl);
rblr.suppressExceptions(true);
rblr.toggle(false);
// rubber band line release
cell_rbl = new CellImpl() {
public void doAction() throws VisADException, RemoteException {
synchronized (lock) {
Set set = (Set) ref_rbl.getData();
if (set == null) return;
float[][] samples = set.getSamples();
if (samples == null) return;
// make sure both ends of set are within grid domain
int[] indices = xyset.valueToIndex(samples);
if (indices[0] < 0 || indices[1] < 0) return;
// find an available PickCell
int index = -1;
for (int i=0; i<npicks; i++) {
if (ref_picks[i].getData() == null) {
// put stroke in available PickCell
index = i;
cell_picks[index].setSkip();
ref_picks[index].setData(set);
rend_picks[index].toggle(true);
break;
}
}
if (index < 0) {
// no PickCell available, so create NPICKS more
index = npicks;
makePicks(npicks + NPICKS);
cell_picks[index].setSkip();
ref_picks[index].setData(set);
rend_picks[index].toggle(true);
}
int modifiers = rblr.getLastMouseModifiers();
if ((modifiers & dialog_mask) != 0) { // yes
FlatField ff = null;
int tindex;
if (t != null) {
tindex = getAnimationIndex();
if (tindex < 0 || tindex >= nts) return;
ff = (FlatField) grids.getSample(tindex);
}
else {
ff = (FlatField) grids;
}
Data range = ff.getSample(indices[0]);
if (range instanceof Real) {
float value = (float) ((Real) range).getValue();
String name = ((RealType) range.getType()).getName();
String question = "increment " + name + " (current: " + value + ")";
String newvs = JOptionPane.showInputDialog(null, question);
float newvalue = 0.0f;
try {
newvalue = Float.parseFloat(newvs);
}
catch (NumberFormatException e) {
}
delta_picks[0][index] = newvalue;
// System.out.println(name + " increment = " + newvalue);
}
else if (range instanceof RealTuple) {
for (int j=0; j<rangedim; j++) {
Real real = (Real) ((RealTuple) range).getComponent(j);
float value = (float) real.getValue();
String name = ((RealType) real.getType()).getName();
String question = "increment " + name + " (default: " + value + ")";
String newvs = JOptionPane.showInputDialog(null, question);
float newvalue = 0.0f;
try {
newvalue = Float.parseFloat(newvs);
}
catch (NumberFormatException e) {
}
delta_picks[j][index] = newvalue;
// System.out.println(name + " increment = " + newvalue);
}
}
}
} // end synchronized (lock)
}
};
}
// make PickCell's etc up from npicks through n-1
private void makePicks(int n) throws VisADException, RemoteException {
if (n <= npicks) return;
PickCell[] tcell = null;
DataReferenceImpl[] tref = null;
PickManipulationRendererJ3D[] trend = null;
float[][] tdelta = null;
if (npicks > 0) {
tcell = cell_picks;
tref = ref_picks;
trend = rend_picks;
tdelta = delta_picks;
}
cell_picks = new PickCell[n];
ref_picks = new DataReferenceImpl[n];
rend_picks = new PickManipulationRendererJ3D[n];
delta_picks = new float[rangedim][n];
if (npicks > 0) {
System.arraycopy(tcell, 0, cell_picks, 0, npicks);
System.arraycopy(tref, 0, ref_picks, 0, npicks);
System.arraycopy(trend, 0, rend_picks, 0, npicks);
for (int j=0; j<rangedim; j++) {
System.arraycopy(tdelta[j], 0, delta_picks[j], 0, npicks);
}
}
display.disableAction();
for (int i=npicks; i<n; i++) makePick(i);
display.enableAction();
npicks = n;
}
// make PickCell etc for index i
private void makePick(int i) throws VisADException, RemoteException {
ref_picks[i] = new DataReferenceImpl("pick" + i);
rend_picks[i] = new PickManipulationRendererJ3D(renderer_mask, renderer_mask);
rend_picks[i].suppressExceptions(true);
rend_picks[i].toggle(false);
cell_picks[i] = new PickCell(ref_picks[i], rend_picks[i]);
cell_picks[i].setSkip();
cell_picks[i].addReference(ref_picks[i]);
display.addReferences(rend_picks[i], ref_picks[i]);
for (int j=0; j<rangedim; j++) delta_picks[j][i] = 0.0f;
}
/**
* enable user to draw move vectors with optional deltas
*/
public void start() throws VisADException, RemoteException {
synchronized (lock) {
cell_rbl.addReference(ref_rbl);
Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
ref_rbl.setData(dummy_set);
rblr.toggle(true);
}
}
/**
* warp grid according to move vectors drawn by user
* also interpolate user defined delta values at move points
*/
public void stop() throws VisADException, RemoteException {
synchronized (lock) {
int np = 0; // number of pick points
for (int i=0; i<npicks; i++) {
if (ref_picks[i].getData() != null) np++;
}
if (np != 0) {
FlatField ff = null;
int index;
if (t != null) {
index = getAnimationIndex();
if (index < 0 || index >= nts) return;
ff = (FlatField) grids.getSample(index);
}
else {
ff = (FlatField) grids;
}
savedff = new FlatField(grid_type, xyset);
savedff.setSamples(ff.getFloats(false), false);
float[][][] set_samples = new float[np][][];
float[][] deltas = new float[rangedim][np];
int k = 0;
for (int i=0; i<npicks; i++) {
if (ref_picks[i].getData() != null) {
for (int j=0; j<rangedim; j++) deltas[j][k] = delta_picks[j][i];
set_samples[k++] = ((Set) ref_picks[i].getData()).getSamples(false);
}
}
// TO DO: set deltas = null if all 0.0f
FlatField newff = warpGrid(ff, set_samples, deltas);
ff.setSamples(newff.getFloats(false), false);
replacedff = ff;
} // end if (np != 0)
display.disableAction();
rblr.toggle(false);
for (int i=0; i<npicks; i++) {
cell_picks[i].setSkip();
ref_picks[i].setData(null);
rend_picks[i].toggle(true);
}
for (int j=0; j<rangedim; j++) {
for (int i=0; i<npicks; i++) delta_picks[j][i] = 0.0f;
}
display.enableAction();
try { cell_rbl.removeReference(ref_rbl); }
catch (ReferenceException e) { }
}
}
/**
* warpGrid is the workhorse of GridEdit and can be used independently
* of any instances of the class
* @param ff A FlatField containing the 2-D grid to be warped.
* @param set_samples The move vectors, dimensioned [number_of_moves][2][2]
* where the second index enumerates x and y and the third
* index enumerates the "from" and "to" ends of the move.
* These values are x and y data values
* @param deltas Increments for moved values, to be interpolated over the
* grid, dimensioned [number_of_grid_range_values][number_of_moves].
* May be null.
* @return Warped grid, with motion vectors and deltas applied.
* @throws VisADException bad parameters
*/
public static FlatField warpGrid(FlatField ff, float[][][] set_samples,
float[][] deltas)
throws VisADException, RemoteException {
if (ff == null || set_samples == null || set_samples.length == 0) {
throw new VisADException("null parameter");
}
FunctionType fft = (FunctionType) ff.getType();
RealTupleType xy = fft.getDomain();
GriddedSet xyset = (GriddedSet) ff.getDomainSet();
MathType range = fft.getRange();
int range_dim = -1;
int np = set_samples.length; // number of move points
if (range instanceof RealType) {
range_dim = 1;
}
else if (range instanceof RealTupleType) {
range_dim = ((RealTupleType) range).getDimension();
}
if (deltas != null) {
if (deltas.length != range_dim) {
throw new VisADException("deltas bad length");
}
for (int j=0; j<range_dim; j++) {
if (deltas[j] == null || deltas[j].length != np) {
throw new VisADException("deltas bad length");
}
}
}
int nx = xyset.getLength(0);
int ny = xyset.getLength(1);
// get locations of grid border points, in order
/*
// every point on border
int nb = 2 * (nx + ny) - 4; // number of grid border points
int[] indicesb = new int[nb];
int k = 0;
for (int i=0; i<nx; i++) {
indicesb[k++] = i;
}
for (int i=1; i<(ny-1); i++) {
indicesb[k++] = (i + 1) * nx - 1;
}
for (int i=nx-1; i>=0; i--) {
indicesb[k++] = (ny - 1) * nx + i;
}
for (int i=ny-2; i>=1; i--) {
indicesb[k++] = i * nx;
}
*/
// just 4 corner points on border
// int nb = 4;
// int[] indicesb = {0, (ny - 1) * nx, ny * nx - 1, nx - 1};
// limit number of points on border
int NB = 32;
// int NB = 4;
int nxb = (nx > NB) ? NB : nx;
int nyb = (ny > NB) ? NB : ny;
float xb = ((float) nx) / ((float) nxb);
float yb = ((float) ny) / ((float) nyb);
int nb = 2 * (nxb + nyb) - 4; // number of grid border points
int[] indicesb = new int[nb];
int k = 0;
for (int i=0; i<nxb; i++) {
int ii = Math.round(i * xb);
if (ii < 0) ii = 0;
if (ii > (nx - 1)) ii = nx - 1;
indicesb[k++] = ii;
}
for (int i=1; i<(nyb-1); i++) {
int ii = Math.round(i * yb);
if (ii < 1) ii = 1;
if (ii > (ny - 2)) ii = ny - 2;
indicesb[k++] = (ii + 1) * nx - 1;
}
for (int i=nxb-1; i>=0; i--) {
int ii = Math.round(i * xb);
if (ii < 0) ii = 0;
if (ii > (nx - 1)) ii = nx - 1;
indicesb[k++] = (ny - 1) * nx + ii;
}
for (int i=nyb-2; i>=1; i--) {
int ii = Math.round(i * yb);
if (ii < 1) ii = 1;
if (ii > (ny - 2)) ii = ny - 2;
indicesb[k++] = ii * nx;
}
float[][] samplesb = xyset.indexToValue(indicesb); // locations of grid border points
// check if all move "to" points are inside 0.8 radius of circle
float[][] set_locs = new float[2][np];
for (int i=0; i<np; i++) {
set_locs[0][i] = set_samples[i][0][1];
set_locs[1][i] = set_samples[i][1][1];
}
float[][] set_grid = xyset.valueToGrid(set_locs);
float nx2 = (nx - 1.0f) / 2.0f;
float ny2 = (ny - 1.0f) / 2.0f;
float nm2 = Math.min(nx2, ny2);
float nm22 = nm2 * nm2;
float nx22 = nx2 * nx2;
float ny22 = ny2 * ny2;
boolean all_in = true;
for (int i=0; i<np; i++) {
float d = (set_grid[0][i] - nx2) * (set_grid[0][i] - nx2) / nm22 +
(set_grid[1][i] - ny2) * (set_grid[1][i] - ny2) / nm22;
if (d > 0.64) {
all_in = false;
// System.out.println("not all_in d = " + d);
break;
}
}
if (all_in) {
// all move "to" points inside 0.8 radius, so add circle boundary points
int ne = 32; // weird bug for ne proportional to nx and ny
int new_nb = nb + ne;
float[][] new_samplesb = new float[2][new_nb];
System.arraycopy(samplesb[0], 0, new_samplesb[0], 0, nb);
System.arraycopy(samplesb[1], 0, new_samplesb[1], 0, nb);
float[][] circle_grid = new float[2][ne];
for (int i=0; i<ne; i++) {
double ang = 2.0 * Math.PI * i / (ne);
circle_grid[0][i] = nx2 + nm2 * 0.90f * ((float) Math.sin(ang) );
circle_grid[1][i] = ny2 + nm2 * 0.90f * ((float) Math.cos(ang) );
}
float[][] circle_locs = xyset.gridToValue(circle_grid);
System.arraycopy(circle_locs[0], 0, new_samplesb[0], nb, ne);
System.arraycopy(circle_locs[1], 0, new_samplesb[1], nb, ne);
samplesb = new_samplesb;
nb = new_nb;
}
RealType xmove = RealType.getRealType("xmove");
RealType ymove = RealType.getRealType("ymove");
RealTupleType xymove = new RealTupleType(xmove, ymove);
FunctionType move_type = new FunctionType(xy, xymove);
// combine stationary border points with move points
int ns = nb + np;
float[][] mover = new float[2][ns];
float[][] moved = new float[2][ns];
for (int i=0; i<nb; i++) {
// located at border point
moved[0][i] = samplesb[0][i];
moved[1][i] = samplesb[1][i];
// zero move vector
mover[0][i] = 0.0f;
mover[1][i] = 0.0f;
}
// float[][] ireg = new float[2][np]; // for non-Delaunay IrregularSet
for (int i=0; i<np; i++) {
int ip = nb + i;
float[][] samples = set_samples[i];
// located at "to" end of vector user drew
moved[0][ip] = samples[0][1];
moved[1][ip] = samples[1][1];
// move vector is negative of vector user drew
mover[0][ip] = samples[0][0] - samples[0][1];
mover[1][ip] = samples[1][0] - samples[1][1];
// for non-Delaunay IrregularSet
// ireg[0][i] = samples[0][1];
// ireg[1][i] = samples[1][1];
}
/*
a nice idea, but this accentuates corner effects
// compute Irregular2DSet without any triangles whose vertices
// are all 3 on the border
Irregular2DSet nset = new Irregular2DSet(xy, ireg);
Delaunay del = nset.Delan;
int[][] dtris = null;
if (del != null) dtris = del.Tri;
// find closest internal point to each border point
int[] closest = new int[nb];
for (int i=0; i<nb; i++) {
float dist = Float.MAX_VALUE;
for (int j=0; j<np; j++) {
float d = (moved[0][i] - ireg[0][j]) * (moved[0][i] - ireg[0][j]) +
(moved[1][i] - ireg[1][j]) * (moved[1][i] - ireg[1][j]);
if (d < dist) {
dist = d;
closest[i] = j + nb;
}
}
}
// compute triangles for borders (no triangles with all 3 vertices on border)
int[][] btris = new int[2 * nb][3];
int ntris = 0;
for (int i=0; i<nb; i++) {
int ip = i + 1;
if (ip == nb) ip = 0;
btris[ntris][0] = i;
btris[ntris][1] = ip;
btris[ntris][2] = closest[i];
// System.out.println("a " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
ntris++;
if (closest[i] != closest[ip]) {
btris[ntris][0] = ip;
btris[ntris][1] = closest[i];
btris[ntris][2] = closest[ip];
// System.out.println("b " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
ntris++;
}
}
// add internal triangles
if (dtris != null) {
int nd = dtris.length;
for (int i=0; i<nd; i++) {
btris[ntris][0] = dtris[i][0] + nb;
btris[ntris][1] = dtris[i][1] + nb;
btris[ntris][2] = dtris[i][2] + nb;
// System.out.println("c " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
ntris++;
}
}
int[][] tris = new int[ntris][3];
for (int i=0; i<ntris; i++) {
int a = btris[i][0];
int b = btris[i][1];
int c = btris[i][2];
float cross = (moved[0][b]-moved[0][a]) * (moved[1][c]-moved[1][a]) -
(moved[0][c]-moved[0][a]) * (moved[1][b]-moved[1][a]);
// ensure uniform rotation direction of triangles
if (cross > 0.0f) {
tris[i][0] = a;
tris[i][1] = b;
tris[i][2] = c;
}
else {
tris[i][0] = a;
tris[i][1] = c;
tris[i][2] = b;
}
// System.out.println("d " + tris[i][0] + " " + tris[i][1] + " " + tris[i][2]);
}
DelaunayCustom dc = new DelaunayCustom(moved, tris);
Irregular2DSet iset = new Irregular2DSet(xy, moved, null, null, null, dc);
// end of compute Irregular2DSet without any triangles whose vertices
// are all 3 on the border
*/
// compute Irregular2DSet via simple Delaunay
IrregularSet iset = new Irregular2DSet(xy, moved);
// moveff is vector Field of motions, at Irregular2DSet combining
// stationary border points with inverses of user drawn vectors
FlatField moveff = new FlatField(move_type, iset);
moveff.setSamples(mover);
// interpolate inverse motions to grid locations
FlatField move_interp = (FlatField) moveff.resample(xyset);
// form a new "warped" Gridded2DSet of the locations
// that grid move to under interpolate inverse motions
float[][] bases = xyset.getSamples(true); // copy
float[][] offsets = move_interp.getFloats(false);
for (int i=0; i<nx*ny; i++) {
bases[0][i] += offsets[0][i];
bases[1][i] += offsets[1][i];
}
Gridded2DSet warpset =
new Gridded2DSet(xy, bases, nx, ny, null, null, null, false, false);
// interpolated grid values at locations of warped grid
FlatField warpff = (FlatField) ff.resample(warpset);
// put interpolated values at warped grid into a new grid
// at the original grid locations, and return it
FlatField newff = new FlatField(fft, xyset);
newff.setSamples(warpff.getFloats(false), false);
if (deltas != null) {
// form irregular Field of delta values
FlatField deltaff = new FlatField(fft, iset);
float[][] ds = new float[range_dim][ns];
for (int j=0; j<range_dim; j++) {
for (int i=0; i<nb; i++) ds[j][i] = 0.0f;
System.arraycopy(deltas[j], 0, ds[j], nb, np);
}
deltaff.setSamples(ds, false);
newff = (FlatField) newff.add(deltaff, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
}
// replace any missing newff values with ff values
float[][] ff_values = ff.getFloats(false);
float[][] newff_values = newff.getFloats(false);
boolean any_missing = false;
int nff = ff_values[0].length;
for (int j=0; j<range_dim; j++) {
for (int i=0; i<nff; i++) {
if (newff_values[j][i] != newff_values[j][i]) {
newff_values[j][i] = ff_values[j][i];
any_missing = true;
}
}
}
if (any_missing) newff.setSamples(newff_values);
return newff;
}
/**
* undo action of last call to stop()
*/
public void undo() throws VisADException, RemoteException {
synchronized (lock) {
if (replacedff != null) {
float[][] samples = savedff.getFloats(false);
replacedff.setSamples(samples, false);
}
replacedff = null;
}
stop();
}
// return the index of the current animation step
private int getAnimationIndex() throws VisADException {
int[] indices = {acontrol.getCurrent()};
Set aset = acontrol.getSet();
double[][] values = aset.indexToDouble(indices);
int[] tindices = tset.doubleToIndex(values);
return tindices[0];
}
private static final int NX = 64;
private static final int NY = 64;
private static final int NTIMES = 4;
public static void main(String args[])
throws VisADException, RemoteException {
// construct RealTypes for wind record components
RealType x = RealType.getRealType("x");
RealType y = RealType.getRealType("y");
RealType lat = RealType.Latitude;
RealType lon = RealType.Longitude;
RealTupleType xy = new RealTupleType(x, y);
RealType windx = RealType.getRealType("windx",
CommonUnit.meterPerSecond);
RealType windy = RealType.getRealType("windy",
CommonUnit.meterPerSecond);
RealType red = RealType.getRealType("red");
RealType green = RealType.getRealType("green");
// EarthVectorType extends RealTupleType and says that its
// components are vectors in m/s with components parallel
// to Longitude (positive east) and Latitude (positive north)
EarthVectorType windxy = new EarthVectorType(windx, windy);
RealType time = RealType.Time;
double startt = new DateTime(1999, 122, 57060).getValue();
Linear1DSet time_set = new Linear1DSet(time, startt, startt + 2700.0, NTIMES);
Linear2DSet grid_set = new Integer2DSet(xy, NX, NY);
RealTupleType tuple_type = new RealTupleType(new RealType[]
{lon, lat, windx, windy, red, green});
FunctionType field_type = new FunctionType(xy, tuple_type);
FunctionType seq_type = new FunctionType(time, field_type);
// construct first Java3D display and mappings that govern
// how wind records are displayed
DisplayImplJ3D display1 =
new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
double NM = Math.max(NX, NY);
ScalarMap ymap = new ScalarMap(y, Display.YAxis);
display1.addMap(ymap);
ScalarMap xmap = new ScalarMap(x, Display.XAxis);
display1.addMap(xmap);
ymap.setRange(0.0, NM);
xmap.setRange(0.0, NM);
ScalarMap cmap = null;
if (args.length > 0) {
cmap = new ScalarMap(windy, Display.RGB);
}
else {
cmap = new ScalarMap(windy, Display.IsoContour);
}
display1.addMap(cmap);
ScalarMap amap = new ScalarMap(time, Display.Animation);
display1.addMap(amap);
AnimationControl acontrol = (AnimationControl) amap.getControl();
acontrol.setStep(500);
// create an array of NX by NY winds
FieldImpl field = new FieldImpl(seq_type, time_set);
double[][] values = new double[6][NX * NY];
for (int k=0; k<NTIMES; k++) {
FlatField ff = new FlatField(field_type, grid_set);
int m = 0;
double NX2 = NX / 2.0;
double NY2 = NY / 2.0;
for (int j=0; j<NY; j++) {
for (int i=0; i<NX; i++) {
double u = 2.0 * (i - NX2) / (NM - 1.0);
double v = 2.0 * (j - NY2) / (NM - 1.0);
// each wind record is a Tuple (lon, lat, (windx, windy), red, green)
// set colors by wind components, just for grins
values[0][m] = 10.0 * u;
values[1][m] = 10.0 * v - 40.0;
double fx = k + 30.0 * u;
double fy = 30.0 * v;
double fd =
Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy) + k * 15.0;
double fs = Math.sqrt(fx * fx + fy * fy);
values[2][m] = fd;
values[3][m] = fs;
values[4][m] = u;
values[5][m] = v;
m++;
}
}
ff.setSamples(values);
field.setSample(k, ff);
}
DataReferenceImpl seq_ref = new DataReferenceImpl("seq");
seq_ref.setData(field);
display1.addReference(seq_ref);
final GridEdit cp = new GridEdit(field, display1);
// create JFrame (i.e., a window) for display and slider
JFrame frame = new JFrame("test CollectiveBarbManipulation");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
// create JPanel in JFrame
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
JPanel panel1 = new JPanel();
panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
JPanel panel2 = new JPanel();
panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
panel1.add(display1.getComponent());
panel1.setMaximumSize(new Dimension(400, 600));
JPanel panel3 = new JPanel();
panel3.setLayout(new BoxLayout(panel3, BoxLayout.X_AXIS));
final JButton start = new JButton("start");
start.addActionListener(cp);
start.setActionCommand("start");
final JButton stop = new JButton("stop");
stop.addActionListener(cp);
stop.setActionCommand("stop");
final JButton undo = new JButton("undo");
undo.addActionListener(cp);
undo.setActionCommand("undo");
panel3.add(start);
panel3.add(stop);
panel3.add(undo);
panel2.add(new AnimationWidget(amap));
panel2.add(new JLabel(" "));
if (args.length > 0) {
LabeledColorWidget lcw = new LabeledColorWidget(cmap);
lcw.setMaximumSize(new Dimension(400, 200));
panel2.add(lcw);
}
else {
ContourWidget cw = new ContourWidget(cmap);
cw.setMaximumSize(new Dimension(400, 200));
panel2.add(cw);
}
panel2.add(new JLabel(" "));
panel2.add(panel3);
panel2.setMaximumSize(new Dimension(400, 600));
panel.add(panel1);
panel.add(panel2);
frame.getContentPane().add(panel);
// set size of JFrame and make it visible
frame.setSize(800, 600);
frame.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("start")) {
try {
start();
}
catch (VisADException ex) {
ex.printStackTrace();
}
catch (RemoteException ex) {
ex.printStackTrace();
}
}
else if (cmd.equals("stop")) {
try {
stop();
}
catch (VisADException ex) {
ex.printStackTrace();
}
catch (RemoteException ex) {
ex.printStackTrace();
}
}
else if (cmd.equals("undo")) {
try {
undo();
}
catch (VisADException ex) {
ex.printStackTrace();
}
catch (RemoteException ex) {
ex.printStackTrace();
}
}
}
}
/**
* CellImpl for user clicks to delete move vectors
* via setData(null)
*/
class PickCell extends CellImpl {
private boolean skip;
private DataRenderer rend;
private DataReferenceImpl ref;
public PickCell(DataReferenceImpl rf, DataRenderer rd) {
rend = rd;
ref = rf;
skip = true;
}
public void doAction() throws VisADException, RemoteException {
if (skip) {
skip = false;
}
else {
rend.toggle(false);
skip = true;
ref.setData(null);
}
}
public void setSkip() {
skip = true;
}
}