LINUX.ORG.RU

Java2D Не получается сделать привязку курсора к узлам сетки

 


2

2

Доброго времени суток. Есть JPanel, расчертил на ней сетку, нужно чтобы при движении мышки, курсор(не сам указатель мышки, а просто нарисованный мной крестик) перемещался только по узлам этой сетки. Попробовал реализовать(код ниже). Вроде как всё работает, но немного не так как надо, курсор не совсем точно встаёт в узлы сетки - как-то неправильно высчитываются координаты узлов, причём, если поставить курсор у левой стороны окна(где координата Х меньше), то всё нормально, но чем больше вправо сдвигаюсь по Х, тем сильнее «убегает» курсор, тоже самое и по оси Y. Пояснение на скриншотах. Не пойму в чём причина, грешу на округление, либо как-то не так считаю помогите разобраться (

Немного картинок, чтобы было понятней: Когда всё ровно - http://savepic.ru/10767351.png

Теперь когда сдвигаемся в противоположный край окна - курсор убегает от узла сетки, причём, чем больше сдвиг - тем сильнее «убегание» - http://savepic.ru/10754039.png

тоже самое убегание по Y - http://savepic.ru/10803190.png

Код класса панели, на которой рисую

public class DrawArea extends JPanel implements MouseListener, 
                                                MouseMotionListener, 
                                                MouseWheelListener  {

    private int aDPI = 300;
        
    /** По умолчанию панель будет размером с лист А4 */
    private Dimension aSize = TR_Utils.mmsToPixels(TR_Utils.A4_PAGE_SIZE_MM, aDPI);  
    
     /** Коэффициент зумирования */
    private double  scaleFactor           = 1.0;
    
    /** Размер сетки(во внутренник единицах измерения), если включена */
    private int     gridSize              = 40;
    /** Позиция курсора - позиция от которой рисуем(в логических, мировых координатах) */
    private Point2D crossHairPosition     = new Point2D.Double();
    /** Привязывать ли курсор к сетке */
    private boolean snapCursorToGrid      = true;
    /** Включаем выключаем сетку */
    private boolean isGridEnabled         = true;
    /** Крест по центру экрана, если надо */
    private boolean isCrossEnabled        = true;
    /** Сглаживание, если требуется */
    private boolean isAnlialiasingEnabled = false;
    
    /** Включен режим рисования(рисуется курсор для 
     * рисования на панели, иначе простой указатель) */
    private boolean isDrawMode            = true;
    
    /** Включен ли режим просмотра для панели.
     * В этом режиме запрещено редактировать или что-либо рисовать на панели,
     * события мыши и клавиатуры игнорируются, изображение на панели 
     * панорамируется(если необходимо) */
    private boolean isViewMode            = false;
    
    /** Координата, на которой нажали кнопку */
    private Point mousePressedPos;
    
    //GridSize s = new GridSize(15, SizeMeasureUnits.MIL);
    
    private AffineTransform at = new AffineTransform();
    
    public DrawArea() {
        
        setPreferredSize(aSize);
        
        System.out.println("Size w: " + aSize.width + " h: " + aSize.height);
        
        addMouseMotionListener(this);
        addMouseListener(this);
        addMouseWheelListener(this);    
    }
    
    @Override
    public Dimension getPreferredSize() {
        return new Dimension((int)(aSize.width*scaleFactor), 
                             (int)(aSize.height*scaleFactor));
    }
    
    /** Переводит координаты окна в координаты панели(мировые координаты) */
    private Point TranslateW2WCoordinates(int winX, int winY) {
        Container parent = getParent();
        Point point = null;
        if(parent != null)
            point = SwingUtilities.convertPoint(parent, new Point(winX, winY), this);
        
        return point;
    }
    
    public void setCenterInViewport() {
        Container parent = getParent();
        if (parent instanceof JViewport) {

            JViewport port = (JViewport)parent;
            Rectangle viewRect = port.getViewRect();

            int width = getWidth();
            int height = getHeight();

            viewRect.x = (width - viewRect.width) / 2 - viewRect.x;
            viewRect.y = (height - viewRect.height) / 2 - viewRect.y;
                
            port.scrollRectToVisible(viewRect);
        }
    }
    
    /** Находит ближайший узел сетки для установки курсора.
      * curX, curY - координаты курсора в мировых координатах */
    private Point getNearestGridPosition(int curX, int curY) {
        
        double scaledGridSize = gridSize * scaleFactor; 
               
        int cellx = (int)Math.round(curX / scaledGridSize);
        int celly = (int)Math.round(curY / scaledGridSize);
        
        int x = (int)Math.round(cellx * scaledGridSize);
        int y = (int)Math.round(celly * scaledGridSize);
        
        System.out.println("Grid size: " + gridSize + " ScaleFactor: " + scaleFactor + " Scaled grid size: " + scaledGridSize);
        System.out.println("Cursor pos - x: " + curX + " y: " + curY);
        System.out.println("Cell num - x: " + cellx + " y: " + celly);
        System.out.println("CrossHairPos - x: " + x + " y: " + y);
        System.out.println("--------------------");
        
        return new Point(x, y);
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); 
        Graphics2D g2d = (Graphics2D) g;
        
        setBackground(Color.WHITE);
               
        if(isAnlialiasingEnabled) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                    RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
        }
        
        /* Prepare draw area */
        if(isGridEnabled) {
            int w = getWidth();
            int h = getHeight();
            int gridStep = (int)Math.round(gridSize * scaleFactor);
            
            g2d.setColor(new Color(235, 235, 235));
            for(int i = 0; i < w; i += gridStep) 
                g2d.drawLine(i, 0, i, h);
            
            for(int j = 0; j < h; j += gridStep) 
                g2d.drawLine(0, j , w, j);
        }
        
        if(isCrossEnabled) {
            g2d.setColor(Color.GRAY);
            float[] dash = {2f, 0f, 2f};
            Stroke oldStroke = g2d.getStroke();
            BasicStroke bs = new BasicStroke(1, BasicStroke.CAP_BUTT, 
                                            BasicStroke.JOIN_ROUND, 1.0f, dash, 2f);
            g2d.setStroke(bs);
            g2d.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
            g2d.drawLine(0, getHeight()/2, getWidth(), getHeight()/2);
            g2d.setStroke(oldStroke);
        }
        
        if (isDrawMode) {
            int cs = 12; // размер крестика-курсора в пикселях
            g2d.setColor(Color.GRAY);
            
            /* Координаты курсора в мировых координатах, приведем их к оконным */
            int cx = (int)crossHairPosition.getX(); 
            int cy = (int)crossHairPosition.getY();
            
            Point p = SwingUtilities.convertPoint(this, new Point(cx, cy), getParent());
            
            g2d.drawLine(p.x-cs/2, p.y, p.x+cs/2, p.y);
            g2d.drawLine(p.x, p.y-cs/2, p.x, p.y+cs/2);
        }
      
        /* Отрисовываем всё остальное(рисуем модель) */  
        /*
        g2d.setColor(Color.WHITE);
        g2d.fillRect(100, 100, 80, 160);
        g2d.setColor(Color.BLACK);
        g2d.drawRect(100, 100, 80, 160); */
        
        
        g2d.dispose();
        
    }
    
    public void setDPI(int dpi) {
        aDPI = dpi;
        repaint();
    }
    
    public int getDPI() {
        return aDPI;
    }
    
    public void setGridSize(int sizePx) {
        if(sizePx <= 0)
            sizePx = 1;
        
        gridSize = sizePx;
        repaint();
    }
    
    public void setGridEnabled(boolean enabled) {
        isGridEnabled = enabled;
        repaint();
    }
    
    public void setCrossEnabled(boolean enabled) {
        isCrossEnabled = enabled;
        repaint();
    }
    
    public void setAntialiasingEnabled(boolean enabled) {
        isAnlialiasingEnabled = enabled;
        repaint();
    }
    
    public void setViewModeEnabled(boolean enabled) {
        isViewMode = enabled;
        repaint();
    }
    
    /** Включает режим просмотра и панорамирует всё что нарисовано
     * (чтобы оно влезло в вид) */
    public void setViewModeEnabled(boolean enabled, boolean panning) {
        isViewMode = enabled;
        
        /* Если включено панорамирование, перерисуем всё что есть 
         * предварительно отмасштабировав под размеры viewport-а */
        
        //...
        
        repaint();
    }

    /****** MOUSE EVENTS  ******/
    
    @Override
    public void mouseClicked(MouseEvent me) {}

    @Override
    public void mousePressed(MouseEvent me) {
        mousePressedPos = new Point(me.getPoint());
        System.out.println("mousePressedPos " + mousePressedPos.x + "/" + mousePressedPos.y);
    }

    @Override
    public void mouseReleased(MouseEvent me) {
        
    }

    @Override
    public void mouseEntered(MouseEvent me) {}

    @Override
    public void mouseExited(MouseEvent me) {}

    @Override
    public void mouseDragged(MouseEvent me) {
        if(!isViewMode) {
            if(SwingUtilities.isMiddleMouseButton(me)) {
                if(mousePressedPos != null) {

                    JViewport port = (JViewport)getParent();
                    Rectangle viewRect = port.getViewRect(); 

                    int deltaX = mousePressedPos.x - me.getX();
                    int deltaY = mousePressedPos.y - me.getY();

                    viewRect.x += deltaX;
                    viewRect.y += deltaY;

                    scrollRectToVisible(viewRect);
                }
            }
        }
    }

    @Override
    public void mouseMoved(MouseEvent me) {
        if(!isViewMode) {
            int x = me.getX();
            int y = me.getY();
            
            // в мировые координаты
            Point p = SwingUtilities.convertPoint(getParent(), new Point(x, y), this);
            JViewport port = (JViewport)getParent();
            Point vp = port.getViewPosition();

            //System.out.println( "view.x: " + x + " view.y: " + y + 
            //                    " / area.x: " + p.x + " area.y: " + p.y + 
            //                    " / vpos.x: " + vp.x + " vpos.y: " + vp.y);
            
            if(snapCursorToGrid) {
                // Привязываем курсор к узлам сетки
                Point np = getNearestGridPosition(p.x, p.y);
                crossHairPosition.setLocation(np.x, np.y);
            }
            else {
                crossHairPosition.setLocation(p.x, p.y);
            }
            repaint();
        }
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent mwe) {
        if(!isViewMode) {
            /* минус для изменения направления движения колесика при маштабировании, так удобнее */
            scaleFactor += -(0.035 * mwe.getWheelRotation());
            scaleFactor = Math.max(0.05, scaleFactor);
            scaleFactor = Math.min(scaleFactor, 4);
            revalidate();
            repaint();
        }
    }
}

P.S. Пытался убрать код под кат, не получилось (

★★★★★

        int scaledGridSize = gridSize * scaleFactor; 
               
        int cellx = (int)Math.round(curX / scaledGridSize);
        int celly = (int)Math.round(curY / scaledGridSize);
        
        int x = cellx * scaledGridSize;
        int y = celly * scaledGridSize;

попробуй так, я когда то этот вопрос решал и помню решение было простым, но что там было - забыл 8)

subwoofer ★★★★★ ()
Ответ на: комментарий от subwoofer

Спасибо что натолкнули на мысль, получилось. Проблема была в функции рисования, нужно было убрать округление при вычислении размера сетки, т.к получалось, что при отрисовке я округляю, а при вычислении позиции курсора тупо обрезаю дробную часть приведением к int-у. Убрал округление и при вычислении и при отрисовке сетки, тупо привожу к int, вроде работает )

int x = cellx * (int)scaledGridSize;
int y = celly * (int)scaledGridSize;

// ...
if(isGridEnabled) {
            int w = getWidth();
            int h = getHeight();
            int gridStep = (int)(gridSize * scaleFactor);
}

xterro ★★★★★ ()
Ответ на: комментарий от subwoofer

int cellx = (int)Math.round(curX / scaledGridSize);

Лучше здесь использовать Math.floor().

ii8_ ★★★★ ()
Ответ на: комментарий от subwoofer

дык яж про это. floor округляет до целых.

ii8_ ★★★★ ()
Ответ на: комментарий от ii8_

А round()? Я думал оно тоже до целых?

Сейчас заметил, что если не двигать скролбарами, то все нормально, если же ими порулить(панель лежит в JScrollArea) то появляется такое же отклонение от узлов сетки. Чертовщина какая-то :(

xterro ★★★★★ ()
Последнее исправление: xterro (всего исправлений: 1)
Ответ на: комментарий от ii8_

Спасибо за пример, учту на будущее, но всё равно проблему «убегания» курсора от узла сетки не решает(если двигать скролбарами). Буду пробовать изменять размер сетки где-нибудь в одном месте, например в событии движения мышки, чтобы не округлять в одно и тоже в нескольких местах (

P.S. Можно подробнее, почему нельзя делать int/int и использовать round()?

xterro ★★★★★ ()
Ответ на: комментарий от xterro

проблему «убегания» курсора от узла сетки не решает(если двигать скролбарами).

потому как обновление положения твоего крестика завязаны на дижения мыши. Можешь при таже обновлятье его положение или инициировать подобные эвенты.

Можно подробнее, почему нельзя делать int/int и использовать round()?

ну раздели в жабе два инта, к примеру 2/3. Как тебе результат? Round можно и нужно, тут главное понимать какое именно округление тебе требуется.

ii8_ ★★★★ ()
Ответ на: комментарий от xterro

Сейчас заметил, что если не двигать скролбарами, то все нормально, если же ими порулить(панель лежит в JScrollArea) то появляется такое же отклонение от узлов сетки.

Значит ты лажаешь с insets/border/viewport.

no-dashi ★★★★★ ()
Ответ на: комментарий от ii8_

ну раздели в жабе два инта, к примеру 2/3. Как тебе результат?

Джаву, конечно, ради этого запускать не стал, но результат и так понятен и специфицирован — получится 0, странно ожидать чего-то другого от целочисленного деления. В чём проблема-то?

Softwayer ★★ ()
Ответ на: комментарий от Softwayer

Не совсем понял почему ты это у меня спрашиваешь...

ii8_ ★★★★ ()

как уже сказали-инт на инт в джаве))))

особо весело после этого пейсать на пейтоне и ловить странные баги присваивания целочисленого деления не целочесленным типам)))

jsi36331 ()
Ответ на: комментарий от ii8_

Да, странно конечно, что 0, но в тоже время, для «указателя» в данном случае не сильно важно, какое округление используется, в большую или меньшую сторону, просто курсор будет, к примеру, на левом-верхнем узле вместо вехрнего-правого. Мне больше интересно, как движения мыши и скроллбары сказываются на положение «курсора»... ну покрутили скроллбаром, что с того изменилось? а «курсор» уехал (

xterro ★★★★★ ()
Последнее исправление: xterro (всего исправлений: 1)
Ответ на: комментарий от xterro

блина, это же твой код! Для тебя там не должно быть магии.

ii8_ ★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.