简体   繁体   English

沿着边缘的JUNG动画图像

[英]JUNG animate image along an edge

So I'm using JUNG, which is something new for me. 所以我正在使用JUNG,这对我来说是新事物。 I've already implemented a simple GUI, which displays vertexes and edges and through the use of Transformers I can change shapes and all that. 我已经实现了一个简单的GUI,该GUI显示顶点和边缘,通过使用“变形金刚”,我可以更改形状以及所有这些。 But how would I animate an image along an edge? 但是我将如何为边缘的图像设置动画?

If the edges were straight lines it'd be easy as I know start and end X and Y coords, but the edges can also be BentLines, CubicCurves or QuadCurves. 如果边缘是直线,我知道起点和终点的X和Y坐标会很容易,但是边缘也可以是BentLines,CubicCurves或QuadCurves。 How would I make, say, a car move along the line that has been drawn? 例如,我将如何使汽车沿着画出的线移动?

I've looked at the docs for the PathIterator, but to be honest, I haven't got a clue what it actually does and whether it's apropriate for what I want. 我看过PathIterator的文档,但老实说,我不知道它的实际作用以及它是否适合我的需求。

Any pointers in the right direction would be appreciated! 任何朝着正确方向的指针将不胜感激!

This, in fact, is a bit tricky. 实际上,这有点棘手。

First of all, some contortions are necessary in order to obtain the real edge shape that is painted on the screen. 首先,为了获得绘制在屏幕上的真实边缘形状,必须进行一些扭曲。 Fortunately, the relevant code is already contained in the ShapePickSupport.java class from JUNG. 幸运的是,相关的代码已经包含在JUNG的ShapePickSupport.java类中。

Then, one has to compute a point on this shape (which is therefore implicitly assumed to be a line). 然后,必须在该形状上计算一个点(因此隐式假定为直线)。 This involves playing around with the PathIterator for computing the total length, and the current position on the line. 这涉及到使用PathIterator来计算总长度以及在线上的当前位置。

I tried to implement this (in a very basic and simple form) and encapsulate this in an ImageAtEdgePainter class: It receives the VisualizationViewer for the edge shape computations, as well as the edge and the image that should be painted. 我试图实现它(以一种非常基本和简单的形式)并将其封装在ImageAtEdgePainter类中:它接收VisualizationViewer进行边缘形状计算以及边缘和应绘制的图像。 It has a setImageLocation method that accepts a value between 0.0 and 1.0, where 0.0 means that the image should be at the start of the edge, and 1.0 means that the image is at the end of the edge, respectively. 它具有一个setImageLocation方法,该方法接受一个介于0.0和1.0之间的值,其中0.0表示图像应位于边缘的起点,而1.0表示图像应位于边缘的终点。

Using a dummy graph and a dummy image, the result looks like this: 使用伪图形和伪图像,结果如下所示:

ImageAtEdge

where the image oscillates between the end points of the edge. 图像在边缘的端点之间振荡。 Here is the code, as an MCVE : 这是作为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;
    }
}

When you say that you want to move a car along the line, I can imagine that you also want to align the image of the car with the edge - that is, to rotate the image so that the car always points towards the end of the edge. 当您说要沿直线移动汽车时,我可以想象您还希望汽车的图像与边缘对齐 -也就是说, 旋转图像以使汽车始终指向汽车的末端。边缘。 This would not be sooo difficult. 这并不困难。 But if this is an issue, you should probably first have a look at other questions (like Java: Rotate image towards mouse position? ) to see whether the answers there can... "inspire" you, or ask it as a separate (not-JUNG-specific) question. 但是,如果这是一个问题,您可能首先应该看看其他问题(例如Java:将图像旋转到鼠标位置? ),看看那里的答案是否可以...“激发”您的灵感,或作为一个单独的问题问( (非荣格专用)问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM