Java Bindings for HybMesh

Usage

Java wrapper consists of Hybmesh.java source file and core_hmconnection_java library. To compile it you need Java7 (or higher) development kit. Source file can be copied to target application source directory. There is no mechanism to define location of shared library within java code. To make application see that library it should be run with -Djava.library.path flag as

java -Djava.library.path=directory/containing/core_hmconnection_java Application

To set custom path of hybmesh executable assign static property

Hybmesh.hybmesh_exec_path = "directory/containing/hybmesh";

before the first use of Hybmesh class.

Besides basic geometrical and exception classes Hybmesh superclass also provides 2 additional nested classes defining 2D and 3D Point which are used to pass point arguments to hybmesh methods.

Unfortunately Java lacks default function argument notation which is heavily used by Hybmesh design. So all arguments should be defined making function calls somewhat bulky.

Default argument values which are normally used by other interfaces are listed in respective javadocs. For non-primitive data types (i.e. geometrical objects, arrays, strings etc.) arguments with default values can be assigned with null. If so they will be automatically reassigned to their default values.

For example this is a function for importing surface as it is defined in Hybmesh.java file.

/**
 * Imports surface from hybmesh native format.
 *
 * See details in hybmeshpack.hmscript.import3d_surface_hmc().
 *
 * @param surfname if null then ""
 * @param allsurfs default is false
 */
public Surface3D[] import3DSurfaceHmc(String fname, String surfname, boolean allsurfs)
        throws Hybmesh.EUserInterrupt, Hybmesh.ERuntimeError{
    // ....
}

Here the fname argument should be defined by user, surfname could be assigned with null (which will lead to default empty string), allsurfs should be explicitly set to false to provide default behavior.

Hybmesh class implements Autocloseable interface so superclass instances can be created within try-with-resources block to guarantee subprocess exit at its end. Otherwise it won’t be closed until it is captured by garbage collector.

try (Hybmesh hm = new Hybmesh()){
    //....
}

For detailed description of all methods consult python wrapper reference and embedded javadocs of Hybmesh.java file.

Helloworld Example

After installation of hybmesh program copy Hybmesh.java and the following Test.java into empty directory.

Test.java
class Test{
    public static void main(String[] args) throws Exception{
        try (Hybmesh hm = new Hybmesh()){
            Hybmesh.Grid2D g2 = hm.addUnfRectGrid(
                    new Hybmesh.Point2(0, 0),
                    new Hybmesh.Point2(1, 1),
                    2, 2, null);
            System.out.printf("number of cells: %d\n", g2.dims()[2]);
        }
    }
}

Open terminal at this directory and execute replacing java.library.path with a correct path and using full path to javac if needed

>>> javac Test.java Hybmesh.java
>>> java -Djava.library.path="C:/Program Files/Hybmesh/include/java" Test

Introductory Example

This example illustrates the creation of toy GUI application which makes mapping of regular unit square grid into user-defined quadrangle with additional custom mapping points. Graphics is written using Java swing library.

To compile and run this application create a directory containing App.java and GridData.java files listed below along with wrapper Hybmesh.java source file. Then execute following commands (adjusting directory path)

javac App.java GridData.java Hybmesh.java
java -Djava.library.path=/path/to/core_hmconnection_java App

Here is the screenshot of the program as it appears in KDE system:

_images/javaexample.png

To define custom mapping points turn on Add refpoints button and click on the contours. By turning on Move refpoints you can drag defined points along contours and move target contour base vertices with the mouse. With Remove refpoints click on defined points to delete them. Basic grid segmentation is defined in a dialog activated by Basic grid button. Grid mapping is performed at Run mapping click.

All defined points are indexed. The number and order of points in basic and target contours should match as this is a requirement of Grid mapping algorithm.

In this application Grid mapping procedure provides creation of Progress graphic dialog with cancellation support.

Program source consists of two files:

  • App.java contains visual interface code,
  • GridData.java contains code needed to build and store hybmesh grids.

Procedure call with graphical callback support is provided by App.java.ProgressBarExecutor.execute() static method.

Warning

For illustration and testing purposes callback function of current application provides intentional 0.3 second delay which is defined in App.java:ProgressBarExecutor.Popup.callback() function. It could be safely removed.

App.java
import java.util.ArrayList;
import javax.swing.*;
import java.beans.*;
import java.awt.*;
import java.awt.event.*;

public class App extends JFrame implements ActionListener{
    public static final long serialVersionUID = 1L;

    // Entry point
    public static void main(String[] args){
        try{ 
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                GridData gd = new GridData();
                public void run() { createAndShowGUI(gd); }
            });
        } catch(Exception e){ 
            e.printStackTrace(); 
        } 
    }

    Drawer tab1, tab2;
    GridData gd;
    JToggleButton togAdd, togMove, togRem;

    private static void createAndShowGUI(GridData gd){
        JFrame app = new App(gd);
        app.pack();
        app.setLocationRelativeTo(null);
        app.setVisible(true);
    }

    App(GridData gd){
        super("Hybmesh/java mapping demo");
        this.gd = gd;
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //Create the toolbar.
        JToolBar toolbar = new JToolBar();
        toolbar.setFloatable(false);
        addButtons(toolbar);
    
        //Create split pane
        tab1 = new Drawer(gd.from, this);
        tab2 = new Drawer(gd.to, this);
        JSplitPane splitPane = new JSplitPane(
            JSplitPane.HORIZONTAL_SPLIT, tab1, tab2);
        splitPane.setDividerLocation(400);

        //main panel
        add(new JPanel(new BorderLayout()));
        getContentPane().setPreferredSize(new Dimension(800, 400));
        getContentPane().add(toolbar, BorderLayout.PAGE_START);
        getContentPane().add(splitPane, BorderLayout.CENTER);
    }

    // checks for what toggle button is pressed now. Used in Drawer mouse click handle.
    public boolean statusAddRef(){ return togAdd.isSelected(); }
    public boolean statusMoveRef(){ return togMove.isSelected(); }
    public boolean statusRemoveRef(){ return togRem.isSelected(); }

    void addButtons(JToolBar jtb){
        setButton(togAdd = new JToggleButton("Add refpoints"), "add", jtb);
        setButton(togMove = new JToggleButton("Move refpoints"), "move", jtb);
        setButton(togRem = new JToggleButton("Remove refpoints"), "remove", jtb);
        jtb.addSeparator();
        setButton(new JButton("Basic grid"), "basic", jtb);
        setButton(new JButton("Run mapping"), "run", jtb);
    }

    void setButton(AbstractButton b, String act, JToolBar jtb){
        jtb.add(b);
        b.setActionCommand(act);
        b.addActionListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        if ("add".equals(cmd)){
            togMove.setSelected(false); togRem.setSelected(false);
        } else if ("move".equals(cmd)){
            togAdd.setSelected(false); togRem.setSelected(false);
        } else if ("remove".equals(cmd)){
            togAdd.setSelected(false); togMove.setSelected(false);
        } else if ("basic".equals(cmd)){
            //Build a dialog to ask new dimensions of the basic grid
            JSpinner nxField = new JSpinner(new SpinnerNumberModel(10, 1, 99, 1));
            JSpinner nyField = new JSpinner(new SpinnerNumberModel(10, 1, 99, 1));
            final JComponent[] inputs = new JComponent[] {
                new JLabel("Nx"), nxField,
                new JLabel("Ny"), nyField,
            };
            int result = JOptionPane.showConfirmDialog(this, inputs,
                "Enter grid segmentation", JOptionPane.PLAIN_MESSAGE);
            if (result != JOptionPane.OK_OPTION) return;
            // call builder function. It could throw.
            try{
                int nx = (int)nxField.getValue();
                int ny = (int)nyField.getValue();
                gd.setBasicSegmentation(nx, ny);
                tab1.validate();
                tab1.repaint();
            } catch (Exception ee){
                handleException(ee);
            }
        } else if ("run".equals(cmd)){
            //build a dialog to set mapping method
            String[] algos = {"Direct Laplace", "Inverse Laplace"};
            JComboBox<String> algoField = new JComboBox<String>(algos);
            final JComponent[] inputs = new JComponent[] {
                new JLabel("Algorithm"), algoField
            };
            if (JOptionPane.showConfirmDialog(null, inputs, "Map grid options",
                JOptionPane.PLAIN_MESSAGE) != JOptionPane.OK_OPTION) return;

            String ta = (String)algoField.getSelectedItem();
            final String algo = (algos[0].equals(ta)) ? "direct_laplace"
                                                      : "inverse_laplace";
            //call building method with progress bar popup window
            try{
                ProgressBarExecutor.execute(new ProgressBarExecutor.IExec(){
                    public void run(Hybmesh.ICallback cb) throws Exception{
                        gd.doMapping(cb, algo);
                    }
                });
                tab2.validate();
                tab2.repaint();
            } catch (Exception ee){
                handleException(ee);
            }
        }
    }

    // shows message boxes for errors which have occured in grid building routines
    void handleException(Exception e){
        try{
            throw e;
        } catch (Hybmesh.EUserInterrupt ee){
            JOptionPane.showConfirmDialog(this, "Interrupted",
                    "Interrupted", JOptionPane.PLAIN_MESSAGE);
        } catch (Hybmesh.ERuntimeError ee){
            JTextArea textArea = new JTextArea(6, 80);
            textArea.setText(ee.getMessage());
            textArea.setFont(textArea.getFont().deriveFont(12f));
            textArea.setEditable(false);
            JScrollPane scrollPane = new JScrollPane(textArea);
            final JComponent[] inputs = new JComponent[] { scrollPane };
            JOptionPane.showMessageDialog(this, inputs,
                "Hybmesh runtime error", JOptionPane.ERROR_MESSAGE);
        } catch (Exception ee){
            JOptionPane.showMessageDialog(this, ee.getMessage(),
                    "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
}

// Transforms coordingate from screen to physical
class CoordinateTransform{
    int margin = 20;
    Rectangle canvas;
    double xmin, xmax, ymin, ymax;

    public CoordinateTransform(Rectangle canvas,
            double xmin, double ymin, double xmax, double ymax){
        this.canvas = canvas;
        this.xmin = xmin; this.xmax = xmax;
        this.ymin = ymin; this.ymax = ymax;
        //adjust bounding box to keep aspect ratio.
        double rel1 = (double)canvas.height/canvas.width;
        double Ly = rel1 * (xmax - xmin) - (ymax - ymin);
        double Lx = (ymax-ymin)/rel1 - (xmax - xmin);
        if (Ly > 0){
            this.ymin -= Ly/2; this.ymax += Ly/2;
        } else {
            this.xmin -= Lx/2; this.xmax += Lx/2;
        }
    }
    public int x(double rx){
        return (int)((canvas.width-2*margin)*(rx-xmin)/(xmax-xmin)) + margin;
    }
    public int y(double ry){
        return (int)((canvas.height-2*margin)*(ymax-ry)/(ymax-ymin)) + margin;
    }
    public double realx(int x){
        return (double)(x-margin)/(canvas.width-2*margin)*(xmax-xmin)+xmin;
    }
    public double realy(int y){
        return -((double)(y-margin)/(canvas.height-2*margin)*(ymax-ymin)-ymax);
    }
}

// Component for drawing grid, contour and vertices
class Drawer extends JPanel implements MouseListener, MouseMotionListener{
    public static final long serialVersionUID = 1L;
    GridData.Grid target;
    CoordinateTransform transf;
    App app;
    int moved_point = -1;
    int point_radius = 6;

    public Drawer(GridData.Grid target, App app){
        this.target = target;
        this.app = app;
        addMouseListener(this);
        addMouseMotionListener(this);
        setBackground(Color.WHITE);
    }

    void refreshTransf(){
        Rectangle rect = getBounds();
        transf = new CoordinateTransform(rect,
                target.minX(), target.minY(), target.maxX(), target.maxY());
    }

    void paintGrid(Graphics2D g){
        g.setColor(Color.BLACK);
        g.setStroke(new BasicStroke(1));
        for (int i=0; i<target.edge_vert.length; i+=2){
            g.drawLine(transf.x(target.vertices[2*target.edge_vert[i]]),
                transf.y(target.vertices[2*target.edge_vert[i]+1]),
                transf.x(target.vertices[2*target.edge_vert[i+1]]),
                transf.y(target.vertices[2*target.edge_vert[i+1]+1]));
        }
    }

    void paintContour(Graphics2D g){
        g.setColor(Color.BLUE);
        g.setStroke(new BasicStroke(3));
        int[] x = new int[4], y = new int[4];
        for (int i=0; i<4; ++i){
            x[i] = transf.x(target.corner_vert.get(i).x);
            y[i] = transf.y(target.corner_vert.get(i).y);
        }
        g.drawPolygon(x, y, 4);
    }

    void drawPoint(Graphics2D g, Hybmesh.Point2 p, String s){
        g.fillOval(transf.x(p.x)-point_radius, transf.y(p.y)-point_radius,
            2*point_radius, 2*point_radius);
        g.drawString(s, transf.x(p.x)+point_radius, transf.y(p.y));
    }
    void paintPoints(Graphics2D g){
        g.setColor(Color.BLACK);
        g.setStroke(new BasicStroke(3));
        ArrayList<Hybmesh.Point2> pts = target.corner_vert;
        for (int i=0; i<pts.size(); ++i){
            drawPoint(g, pts.get(i), Integer.toString(i));
        }
    }
    void paintUPoints(Graphics2D g){
        g.setColor(Color.BLUE);
        g.setStroke(new BasicStroke(3));
        for (int i=0; i<target.user_defined_vert.size(); ++i){
            drawPoint(g, target.udefToPoint(i), Integer.toString(4+i));
        }
    }

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        refreshTransf();
        //1) grid
        if (target.hm_grid != null) paintGrid((Graphics2D)g);
        //2) contour
        paintContour((Graphics2D)g);
        //3) corner points
        paintPoints((Graphics2D)g);
        //4) user defined points
        paintUPoints((Graphics2D)g);
    }
    @Override
    public void mouseClicked(MouseEvent e){ }
    @Override
    public void mouseExited(MouseEvent e){ }
    @Override
    public void mouseEntered(MouseEvent e){ }
    @Override
    public void mousePressed(MouseEvent e){
        double x = transf.realx(e.getX()), y = transf.realy(e.getY());
        if (app.statusAddRef()){
            target.addUserDefinedPoint(x, y);
            validate();
            repaint();
        } else if (app.statusMoveRef()){
            moved_point = target.closestVertex(x, y);
        } else if (app.statusRemoveRef()){
            int rem_point = target.closestVertex(x, y);
            target.removeVertex(rem_point);
            validate();
            repaint();
        }
    }
    @Override
    public void mouseReleased(MouseEvent e){
        if (app.statusMoveRef()){ moved_point = -1; }
    }
    @Override
    public void mouseMoved(MouseEvent e) { }
    @Override
    public void mouseDragged(MouseEvent e) {
        if (moved_point != -1){
            double x = transf.realx(e.getX()), y = transf.realy(e.getY());
            target.moveVertex(moved_point, x, y);
            validate();
            repaint();
        }
    }
}

// Provides static method to call grid building routine with popup progress-bar/cancel dialog
class ProgressBarExecutor{
    public static final long serialVersionUID = 1L;

    //Popup window form.
    static class Popup extends JDialog implements Hybmesh.ICallback,
                                                  PropertyChangeListener{
        public static final long serialVersionUID = 1L;
        JLabel lab1, lab2;
        JProgressBar pb1, pb2;
        boolean isCancelled = false;

        public Popup(){
            super((JFrame)null, "Hybmesh operation", true);
            setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
            lab1 = new JLabel("n1");
            lab2 = new JLabel("n2");
            pb1 = new JProgressBar(0, 100);
            pb2 = new JProgressBar(0, 100);
            JButton cancel_button = new JButton("Cancel");
            cancel_button.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    isCancelled = true;
                }
            });
            JOptionPane pane = new JOptionPane(
                new Object[]{lab1, pb1, lab2, pb2},
                JOptionPane.PLAIN_MESSAGE,
                JOptionPane.DEFAULT_OPTION, null,
                new Object[]{cancel_button});
            setContentPane(pane);
            setSize(new Dimension(400, 200));
            setResizable(false);
            setLocationRelativeTo(null);
            addPropertyChangeListener(this);
        }
        @Override
        public void propertyChange(PropertyChangeEvent e){
            String nm = e.getPropertyName();
            if ("n1".equals(nm)) lab1.setText((String)e.getNewValue());
            else if ("n2".equals(nm)) lab2.setText((String)e.getNewValue());
            else if ("p1".equals(nm)) pb1.setValue((Integer)e.getNewValue());
            else if ("p2".equals(nm)){
                int v = (Integer)e.getNewValue();
                if (v < 0){
                    // Second progress is not defined for this subprocess.
                    // We put progress bar into indeterminate state with
                    // constantly moving slider.
                    pb2.setIndeterminate(true);
                } else{
                    pb2.setIndeterminate(false);
                    pb2.setValue(v);
                }
            }
        }
        @Override
        public int callback(String n1, String n2, double p1, double p2){
            try{
                if (isCancelled) throw new InterruptedException();
                //Using events to change window components because
                //this function is called from a thread separated from gui.
                firePropertyChange("n1", null, n1);
                firePropertyChange("n2", null, n2);
                firePropertyChange("p1", null, new Integer((int)(100*p1)));
                firePropertyChange("p2", null, new Integer((int)(100*p2)));
                //!!!! Intentional delay for testing purpose.
                Thread.sleep(300);
                return 0;
            } catch (InterruptedException e){
                return 1;
            }
        }
    };

    // Backgroung worker which runs building routine in a separate thread.
    static class Worker extends SwingWorker<Exception, Void>{
        IExec func;
        Popup cb;
        Worker(IExec func, Popup cb){
            this.func = func;
            this.cb = cb;
            execute();
            cb.setVisible(true);
        }
        @Override
        public Exception doInBackground(){ 
            try{
                func.run(cb);
                return null;
            } catch (Exception e){
                return e;
            }
        }
        @Override
        public void done(){ cb.setVisible(false); }
    }

    // Interfaces for grid building routine
    public static interface IExec{
        public void run(Hybmesh.ICallback cb) throws Exception;
    }

    // Main static function. Rethrows all grid building exceptions.
    // If process was cancelled throws Hybmesh.EUserInterrupt.
    public static void execute(IExec func) throws Exception{
        Worker worker = new Worker(func, new Popup());
        if (worker.get() != null) throw worker.get();
    }
};
GridData.java
import java.util.ArrayList;
import static java.lang.Math.*;

// Stores left and right grids and defined points
public class GridData{
    Hybmesh hm;
    Grid from, to;

    public GridData() throws Exception{
        // Establish hybmesh connection
        hm = new Hybmesh();
        // build base grid with default partition
        from = Grid.sqrGrid(hm, 10, 10);
        to = Grid.sqrContour();
        from.allow_contour_move = false;
        to.allow_contour_move = true;
    }

    // Sets basic grid segmentation
    public void setBasicSegmentation(int nx, int ny) throws Exception{
        Grid tmp = Grid.sqrGrid(hm, nx, ny);
        from.hm_grid = tmp.hm_grid;
        from.vertices = tmp.vertices;
        from.edge_vert = tmp.edge_vert;
    }

    // Performs mapping. Signature fits ProgressBarExecutor.IExec interface.
    public void doMapping(Hybmesh.ICallback cb, String algo) throws Exception{
        hm.assignCallback(cb);
        try{
            //1. create target contour
            Hybmesh.Contour2D target_contour = to.mainContour(hm);
            //2. assemble base and target points
            Hybmesh.Point2[] base_points = from.allVertices();
            Hybmesh.Point2[] target_points = to.allVertices();
            //3. do mapping
            Hybmesh.Grid2D ret = hm.mapGrid(
                from.hm_grid, target_contour, base_points, target_points,
                null, null, algo, false, false);
            //4. add to result
            to.fillGrid(ret);
        } finally {
            hm.resetCallback();
        }
    }

    //Grid data storage
    public static class Grid{
        double[] vertices;
        int[] edge_vert;
        Hybmesh.Grid2D hm_grid=null;
        ArrayList<Hybmesh.Point2> corner_vert;
        ArrayList<Double> user_defined_vert;
        boolean allow_contour_move = false;

        public void fillGrid(Hybmesh.Grid2D hm_grid){
            try{
                this.hm_grid = hm_grid;
                vertices = hm_grid.rawVertices();
                edge_vert = hm_grid.rawTab("edge_vert");
            } catch (Exception e){
                this.hm_grid = null; vertices = null; edge_vert = null;
            }
        }
        
        // Bounding box coordinates
        public double minX(){
            return min(min(corner_vert.get(0).x, corner_vert.get(1).x),
                min(corner_vert.get(2).x, corner_vert.get(3).x));
        }
        public double minY(){
            return min(min(corner_vert.get(0).y, corner_vert.get(1).y),
                min(corner_vert.get(2).y, corner_vert.get(3).y));
        }
        public double maxX(){
            return max(max(corner_vert.get(0).x, corner_vert.get(1).x),
                max(corner_vert.get(2).x, corner_vert.get(3).x));
        }
        public double maxY(){
            return max(max(corner_vert.get(0).y, corner_vert.get(1).y),
                max(corner_vert.get(2).y, corner_vert.get(3).y));
        }

        // corner + user-defined vertices
        public Hybmesh.Point2[] allVertices(){
            Hybmesh.Point2[] ret = new Hybmesh.Point2[4 + user_defined_vert.size()];
            for (int i=0; i<4; ++i) ret[i] = corner_vert.get(i);
            for (int i=0; i<user_defined_vert.size(); ++i){
                ret[i+4] = udefToPoint(i);
            }
            return ret;
        }
        // creates a hybmesh contour object from four corner points
        public Hybmesh.Contour2D mainContour(Hybmesh hm){
            try{
                Hybmesh.Point2[] ret = new Hybmesh.Point2[5];
                for (int i=0; i<4; ++i) ret[i] = corner_vert.get(i);
                ret[4] = ret[0];
                return hm.createContour(ret, null);
            } catch (Exception e){
                return null;
            }
        }

        // indexed user-defined point to physical point
        Hybmesh.Point2 udefToPoint(int index){
            ArrayList<Hybmesh.Point2> pts = corner_vert;
            double w = user_defined_vert.get(index);
            int i1, i2;
            if (w > 3){ w-=3; i1 = 3; i2 = 0; }
            else if (w > 2) { w-=2; i1 = 2; i2 = 3;}
            else if (w > 1) { w-=1; i1 = 1; i2 = 2;}
            else { w-=0; i1 = 0; i2 = 1;}
            return new Hybmesh.Point2((1-w)*pts.get(i1).x + (w)*pts.get(i2).x,
                    (1-w)*pts.get(i1).y + (w)*pts.get(i2).y);
        }
        
        //squared distance betwen two points
        double meas(Hybmesh.Point2 p, double x, double y){
            return (p.x - x)*(p.x - x) + (p.y - y)*(p.y - y);
        }
        // projects point to contours i-th section.
        // returns {squared distance to section, [0, 1] coordinate of the closest point}
        double[] sect_project(double x, double y, int isec){
            Hybmesh.Point2 p1 = corner_vert.get(isec);
            Hybmesh.Point2 p2 = corner_vert.get(isec == 3 ? 0 : isec + 1);
            double ax = p2.x - p1.x, ay = p2.y - p1.y;
            double bx = x - p1.x, by = y - p1.y;
            double ksi=(ax*bx+ay*by)/(ax*ax + ay*ay);
            if (ksi>=1) { return new double[] {meas(p2, x, y), 1.0}; }
            else if (ksi<=0) { return new double[]{meas(p1, x, y), 0.0}; } 
            else{
                double[] A = new double[]{
                    p1.y-p2.y, p2.x-p1.x, p1.x*p2.y-p1.y*p2.x};
                double d0 = A[0]*x+A[1]*y+A[2];
                d0 *= d0; d0 /= (A[0]*A[0] + A[1]*A[1]);
                return new double[]{d0, ksi};
            }
        }
        // gives [0, 4] contour coordinate of given point where
        // [0, 1] are normalized coordinates for the first section,
        // [1, 2]           --- // ---       for the secont section, etc.
        Double calcUserDefinedPoint(double x, double y){
            //find closest contour section
            double[] d1 = sect_project(x, y, 0);
            double[] d2 = sect_project(x, y, 1);
            double[] d3 = sect_project(x, y, 2);
            double[] d4 = sect_project(x, y, 3);
            if (d1[0] <= d2[0] && d1[0] <= d3[0] && d1[0] <= d4[0]){
                return new Double(d1[1]);
            } else if (d2[0] <= d1[0] && d2[0] <= d3[0] && d2[0] <= d4[0]){
                return new Double(d2[1]+1.0);
            } else if (d3[0] <= d1[0] && d3[0] <= d2[0] && d3[0] <= d4[0]){
                return new Double(d3[1]+2.0);
            } else{
                return new Double(d4[1]+3.0);
            }
        }
        // adds a point to the user defined list
        public void addUserDefinedPoint(double x, double y){
            user_defined_vert.add(calcUserDefinedPoint(x, y));
        }

        // finds index of closest given vertex.
        // [0, 1, 2, 3] - are indicies of corner points, all other - user defined ones.
        public int closestVertex(double x, double y){
            int ret = -1;
            double meas_min = 1e100;
            //corner vertices
            for (int i=0; i<4; ++i){
                double m = meas(corner_vert.get(i), x, y);
                if (m <= meas_min){ meas_min = m; ret = i; }
            }
            //user vertices
            for (int i=0; i<user_defined_vert.size(); ++i){
                double m = meas(udefToPoint(i), x, y);
                if (m <= meas_min){ meas_min = m; ret = i+4; }
            }
            return ret;
        }

        // moves indexed vertex to x, y location. 
        // Corner points are moved only if allow_contour_move is true.
        public void moveVertex(int ivert, double x, double y){
            if (ivert < 4){
                //corner point
                if (allow_contour_move){
                    corner_vert.get(ivert).x = x;
                    corner_vert.get(ivert).y = y;
                }
            } else {
                user_defined_vert.set(ivert-4, calcUserDefinedPoint(x, y));
            }
        }
        //removes indexed points. Corner points (ivert < 4) can not be removed
        public void removeVertex(int ivert){
            if (ivert >= 4){
                user_defined_vert.remove(ivert-4);
            }
        }

        // initilaizes Grid object with zero grid and 4 corner points
        public static Grid sqrContour(){
            Grid ret = new Grid();
            ret.user_defined_vert = new ArrayList<Double>();
            ret.corner_vert = new ArrayList<Hybmesh.Point2>();
            ret.corner_vert.add(new Hybmesh.Point2(0, 0));
            ret.corner_vert.add(new Hybmesh.Point2(1, 0));
            ret.corner_vert.add(new Hybmesh.Point2(1, 1));
            ret.corner_vert.add(new Hybmesh.Point2(0, 1));
            return ret;
        }

        // initializes Grid object with unity grid and 4 corner points
        public static Grid sqrGrid(Hybmesh hm, int nx, int ny)
                throws Hybmesh.EUserInterrupt, Hybmesh.ERuntimeError{
            Grid ret = sqrContour();
            ret.fillGrid(hm.addUnfRectGrid(
                ret.corner_vert.get(0), ret.corner_vert.get(2), nx, ny, null));
            return ret;
        }
    };
};