java - JUNG 沿边缘的动画图像

标签 java animation graph jung

所以我正在使用 JUNG,这对我来说是新事物。我已经实现了一个简单的 GUI,它显示顶点和边,并且通过使用 Transformer 我可以更改形状等。 但是我该如何沿着边缘制作图像动画呢?

如果边缘是直线,那就很容易了,因为我知道起点和终点 X 和 Y 坐标,但边缘也可以是 BentLines、CubicCurves 或 QuadCurves。比如说,我如何让汽车沿着绘制的线移动?

我已经查看了 PathIterator 的文档,但说实话,我不知道它实际上是做什么的以及它是否适合我想要的。

任何正确方向的指针将不胜感激!

最佳答案

事实上,这有点棘手。

首先,为了获得屏幕上绘制的真实边缘形状,需要进行一些扭曲。幸运的是,相关代码已包含在 JUNG 的 ShapePickSupport.java 类中。

然后,必须计算该形状上的一个点(因此隐式假定为一条线)。这涉及到使用 PathIterator 来计算总长度以及线路上的当前位置。

我尝试实现这个(以非常基本且简单的形式)并将其封装在 ImageAtEdgePainter 类中:它接收 VisualizationViewer用于边缘形状计算,以及应绘制的边缘和图像。它有一个 setImageLocation 方法,接受 0.0 到 1.0 之间的值,其中 0.0 表示图像应位于边缘的开始处,1.0 表示图像位于边缘的末端,分别。

使用虚拟图表和虚拟图像,结果如下所示:

ImageAtEdge

图像在边缘端点之间振荡。这是代码,如 MCVE :

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.MultiLayerTransformer;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;

public class JUNGEdgePathTest 
{
    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static BufferedImage createDummyImage()
    {
        int w = 100;
        int h = 30;
        BufferedImage image = 
            new BufferedImage(w,h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.BLACK);
        g.fillRect(0,0,w,h);
        g.setColor(Color.WHITE);
        g.drawString("Image", 10, 20);
        g.dispose();
        return image;
    }


    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        final Graph<String, String> graph = getGraph();
        final VisualizationViewer<String, String> vv = 
            new VisualizationViewer<String, String>(
                new FRLayout<String, String>(graph));
        final BufferedImage image = createDummyImage();

        String edge = graph.getEdges().iterator().next();
        final ImageAtEdgePainter<String, String> imageAtEdgePainter = 
            new ImageAtEdgePainter<String, String>(vv, edge, image);

        Timer t = new Timer(20, new ActionListener()
        {
            long prevMillis = 0;
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (prevMillis == 0)
                {
                    prevMillis = System.currentTimeMillis();
                }
                long dtMs = System.currentTimeMillis() - prevMillis;
                double dt = dtMs / 1000.0;
                double phase = 0.5 + Math.sin(dt) * 0.5;
                imageAtEdgePainter.setImageLocation(phase);
                vv.repaint();
            }
        });
        t.start();

        vv.addPostRenderPaintable(imageAtEdgePainter);


        f.getContentPane().add(vv);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
    }

    static class ImageAtEdgePainter<V, E> implements VisualizationViewer.Paintable
    {
        private final VisualizationViewer<V, E> vv;
        private final E edge;
        private final BufferedImage image;
        private double imageLocation;

        ImageAtEdgePainter(
            VisualizationViewer<V, E> vv, 
            E edge,
            BufferedImage image)
        {
            this.vv = vv;
            this.edge = edge;
            this.image = image;
        }

        public void setImageLocation(double imageLocation)
        {
            this.imageLocation = imageLocation;
        }

        @Override
        public void paint(Graphics gr)
        {
            Graphics2D g = (Graphics2D)gr;
            Shape shape = getTransformedEdgeShape(vv, vv.getGraphLayout(), edge);
            Point2D p = computePointAt(shape, 0.2, imageLocation);
            //g.setColor(Color.BLUE);
            //g.draw(shape);
            //System.out.println(p);
            gr.drawImage(image, (int)p.getX(), (int)p.getY(), null);
        }
        @Override
        public boolean useTransform()
        {
            return true;
        }

    }



    private static double computeLength(Shape shape, double flatness)
    {
        double length = 0;
        PathIterator pi = shape.getPathIterator(null, flatness);
        double[] coords = new double[6];
        double previous[] = new double[2];
        while (!pi.isDone())
        {
            int segment = pi.currentSegment(coords);
            switch (segment)
            {
                case PathIterator.SEG_MOVETO:
                    previous[0] = coords[0];
                    previous[1] = coords[1];
                    break;

                case PathIterator.SEG_LINETO:
                    double dx = previous[0]-coords[0];
                    double dy = previous[1]-coords[1];
                    length += Math.sqrt(dx*dx+dy*dy);
                    previous[0] = coords[0];
                    previous[1] = coords[1];
                    break;
            }
            pi.next();
        }
        return length;
    }

    public static Point2D computePointAt(
        Shape shape, double flatness, double alpha)
    {
        alpha = Math.min(1.0, Math.max(0.0, alpha));
        double totalLength = computeLength(shape, flatness);
        double targetLength = alpha * totalLength;
        double currentLength = 0;
        PathIterator pi = shape.getPathIterator(null, flatness);
        double[] coords = new double[6];
        double previous[] = new double[2];
        while (!pi.isDone())
        {
            int segment = pi.currentSegment(coords);
            switch (segment)
            {
                case PathIterator.SEG_MOVETO:
                    previous[0] = coords[0];
                    previous[1] = coords[1];
                    break;

                case PathIterator.SEG_LINETO:
                    double dx = previous[0]-coords[0];
                    double dy = previous[1]-coords[1];
                    double segmentLength = Math.sqrt(dx*dx+dy*dy);
                    double nextLength = currentLength + segmentLength;
                    if (nextLength >= targetLength)
                    {
                        double localAlpha = 
                            (currentLength - targetLength) / segmentLength;
                        //System.out.println("current "+currentLength+" target "+targetLength+" seg "+segmentLength);
                        double x = previous[0] + localAlpha * dx;
                        double y = previous[1] + localAlpha * dy;
                        return new Point2D.Double(x,y);
                    }
                    previous[0] = coords[0];
                    previous[1] = coords[1];
                    currentLength = nextLength;
                    break;
            }
            pi.next();
        }
        return null;
    }


    // This method is take from JUNG ShapePickSupport.java
    private static <V, E>  Shape getTransformedEdgeShape(
        VisualizationViewer<V, E> vv, Layout<V, E> layout, E e) {
        Pair<V> pair = layout.getGraph().getEndpoints(e);
        V v1 = pair.getFirst();
        V v2 = pair.getSecond();
        boolean isLoop = v1.equals(v2);
        RenderContext<V, E> rc = vv.getRenderContext();
        MultiLayerTransformer multiLayerTransformer = 
            rc.getMultiLayerTransformer();
        Point2D p1 = multiLayerTransformer.transform(
            Layer.LAYOUT, layout.transform(v1));
        Point2D p2 = multiLayerTransformer.transform(
            Layer.LAYOUT, layout.transform(v2));
        if(p1 == null || p2 == null) 
            return null;
        float x1 = (float) p1.getX();
        float y1 = (float) p1.getY();
        float x2 = (float) p2.getX();
        float y2 = (float) p2.getY();
        AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
        Shape edgeShape = 
            rc.getEdgeShapeTransformer().transform(
                Context.<Graph<V,E>,E>getInstance(
                    vv.getGraphLayout().getGraph(),e));
        if(isLoop) {
            Shape s2 = rc.getVertexShapeTransformer().transform(v2);
            Rectangle2D s2Bounds = s2.getBounds2D();
            xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
            xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
        } else {
            float dx = x2 - x1;
            float dy = y2 - y1;
            double theta = Math.atan2(dy,dx);
            xform.rotate(theta);
            float dist = (float) Math.sqrt(dx*dx + dy*dy);
            xform.scale(dist, 1.0f);
        }
        edgeShape = xform.createTransformedShape(edgeShape);
        return edgeShape;
    }


    public static Graph<String, String> getGraph() 
    {
        Graph<String, String> g = new DirectedSparseGraph<String, String>();
        g.addVertex("v0");
        g.addVertex("v1");
        g.addVertex("v2");
        g.addVertex("v3");
        g.addVertex("v4");
        g.addEdge("e0", "v0", "v1");
        g.addEdge("e1", "v1", "v2");
        g.addEdge("e2", "v2", "v3");
        g.addEdge("e3", "v3", "v4");
        g.addEdge("e4", "v4", "v0");
        g.addEdge("e5", "v1", "v3");
        g.addEdge("e6", "v2", "v4");
        return g;
    }
}

当您说要沿线移动汽车时,我可以想象您还希望将汽车的图像与边缘对齐 - 即旋转 图像,使汽车始终指向边缘的末端。这不会太困难。但如果这是一个问题,您可能应该首先看看其他问题(例如 Java: Rotate image towards mouse position? ),看看那里的答案是否可以......“启发”您,或者将其作为一个单独的(非 JUNG 特定的)提出) 问题。

关于java - JUNG 沿边缘的动画图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26977847/

相关文章:

Java将IP+端口转换为长整型并返回

java - spring 中的自定义注解

java - mongodb geoSpatial 查询不起作用

graph - N-Quad 图 - 我如何使用它们?

java - 类型转换为 Dalvik 格式失败 : Unable to execute dex: java. nio.BufferOverflowException

css - 如何以不同的速度为关键帧设置动画?

javascript - 如何修复 createjs 中未在 Canvas 上显示的预加载 Assets ?

algorithm - 有向图行走 - 至少访问每个节点一次

graph - 巨大的图形可视化

Python 动画时序