Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
271 views
in Technique[技术] by (71.8m points)

java - How can I implement easing functions with a thread

I'm trying to find an efficient, normal, or simple way to implementing easing-functions into my java program. I got the easing function to work but i feel like there is a more efficient way to do it; one that I can't see, possibly because of tunnel vision. here's the code I have; can someone show me what I should do differently or point me in the direction I need to go research

public class slide extends JPanel implements Runnable {

    Thread ease = new Thread(this);
    float total = 0;
    float dur;

    slide() {

        ease.start();
        setLayout(null);

    }

    public float calc(float t, float b, float c, float d) {

        return c * t / d + b;

    }

    public void run() {
        while (true) {
            try {
                if (total < 50) {
                    total += 1;
                } else {
                    ease.stop();
                }
                setBounds(400, Math.round(200 * total / 50 + 0), 250, 150);
                repaint();
                System.out.println(total + " " + dur);
                ease.sleep(10);
            } catch (Exception e) {
            }
        }

    }
}

I tried to implement the calc() method for a linear easing function I found online but it's practically useless because I was forced I couldn't get it to work unless inserted the equation directly into the

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Okay, so animation is a rather complex and in-depth subject, which I'm not going to cover here, it also involves lots of maths which I don't really understand, so we're not going to go into a massive amount depth or detail, there are better people then me who can explain it, you can read about it on the web

So to begin with, we make some assumptions...

Animation is the change over time, where time is variable. Easement is the variation of (in this case) speed over the time. This means that the speed of the animation is variable for any given point in time.

Basically, what we want to do is "normalise" everything. That is, at the start of the animation, time is 0 and at the end it's 1, everything else in between is a fraction between those two values.

If you can think like this, things become much easier. So based on a given point on the timeline, you can make decisions about what should be done. For example, at 50% of the time, you should be half way between your start and end points

Okay, but how does all that help us? If we were to graph a ease-in and ease-out animation, it would look something like...

BellCurve

Where the x-axis is time and the y-axis is speed (between 0 and 1 on both axis). So at any given point along x (in time), we should be able to calculate the speed.

Now, we can do this using some maths with Bézier spine/curve and calculate the speed of the object at a given point on the timeline.

Now, I borrowed most of the code directly from the Timing Framework, but if you're really interested, you can also look at Bézier Curves for your Games: A Tutorial

(nb: I actually did write something like this, then 2 days later, discovered that the Timing Framework had already implemented...was a fun exercise...)

Now, what's important to note about this implementation is that it won't actually return you the speed of the object, but it will return a progression of time along the timeline (0-1), okay, that sounds weird, but what it does allow you to do is calculate the current position between your start and end points (startValue + ((endValue - startValue) * progress)) along the time line

I'm won't go into a lot of detail about this, as I really don't understand the maths, I simply know how to apply it, but basically, we calculate the points (x/y) along the curve, we then normalize these values (0-1) to make it easier to look up.

The interpolate method uses a binary search to find the closest matching point for a given fraction of time and then calculates the speed/y position of that point

public class SplineInterpolator {

    private final double points[];
    private final List<PointUnit> normalisedCurve;

    public SplineInterpolator(double x1, double y1, double x2, double y2) {
        points = new double[]{ x1, y1, x2, y2 };

        final List<Double> baseLengths = new ArrayList<>();
        double prevX = 0;
        double prevY = 0;
        double cumulativeLength = 0;
        for (double t = 0; t <= 1; t += 0.01) {
            Point2D xy = getXY(t);
            double length = cumulativeLength
                            + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX)
                                            + (xy.getY() - prevY) * (xy.getY() - prevY));

            baseLengths.add(length);
            cumulativeLength = length;
            prevX = xy.getX();
            prevY = xy.getY();
        }

        normalisedCurve = new ArrayList<>(baseLengths.size());
        int index = 0;
        for (double t = 0; t <= 1; t += 0.01) {
            double length = baseLengths.get(index++);
            double normalLength = length / cumulativeLength;
            normalisedCurve.add(new PointUnit(t, normalLength));
        }
    }

    public double interpolate(double fraction) {
        int low = 1;
        int high = normalisedCurve.size() - 1;
        int mid = 0;
        while (low <= high) {
            mid = (low + high) / 2;

            if (fraction > normalisedCurve.get(mid).getPoint()) {
                low = mid + 1;
            } else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
                high = mid - 1;
            } else {
                break;
            }
        }
        /*
         * The answer lies between the "mid" item and its predecessor.
         */
        final PointUnit prevItem = normalisedCurve.get(mid - 1);
        final double prevFraction = prevItem.getPoint();
        final double prevT = prevItem.getDistance();

        final PointUnit item = normalisedCurve.get(mid);
        final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
        final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
        return getY(interpolatedT);
    }

    protected Point2D getXY(double t) {
        final double invT = 1 - t;
        final double b1 = 3 * t * invT * invT;
        final double b2 = 3 * t * t * invT;
        final double b3 = t * t * t;
        final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
        return xy;
    }

    protected double getY(double t) {
        final double invT = 1 - t;
        final double b1 = 3 * t * invT * invT;
        final double b2 = 3 * t * t * invT;
        final double b3 = t * t * t;
        return (b1 * points[2]) + (b2 * points[3]) + b3;
    }

    public class PointUnit {

        private final double distance;
        private final double point;

        public PointUnit(double distance, double point) {
            this.distance = distance;
            this.point = point;
        }

        public double getDistance() {
            return distance;
        }

        public double getPoint() {
            return point;
        }

    }

}

If we do something like...

SplineInterpolator si = new SplineInterpolator(1, 0, 0, 1);
for (double t = 0; t <= 1; t += 0.1) {
    System.out.println(si.interpolate(t));
}

We get something like...

0.0
0.011111693284790492
0.057295031944523504
0.16510933001160544
0.3208510585798438
0.4852971690762217
0.6499037832761319
0.8090819765428142
0.9286158775101805
0.9839043020410436
0.999702

Okay, now you're probably thinking, "wait a minute, that's a linear progression!", but it's not, if you graphed it, you would find that the first three and last three values are very close together, and the others spread out by varying degrees, this is our "progress" value, how far along the timeline we should be

So about now, your head should be about to explode (mine is) - this is why I say, use a framework!

But how would you use it?! This is the fun part, now remember, everything is variable, the duration of the animation, the speed of the object over time, the number of ticks or updates, it's all variable...

This is important, as this is where the power of something like this comes in! If for example, the animation is stalled due to some outside factor, this implementation is capable of simply skipping those "frames", rather than getting bottlenecked and staggering. This might sound like a bad thing, but trust me, this is all about fooling the eye into "think" something is changing ;)

(The following is like 8fps, so it's pretty crappy)

Animate

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private int startAt = 0;
        private int endAt;
        private int x = startAt;
        private Timer timer;
        private SplineInterpolator splineInterpolator;
        private long startTime = -1;
        private long playTime = 5000; // 5 seconds

        public TestPane() {
            splineInterpolator = new SplineInterpolator(1, 0, 0, 1);
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startTime < 0) {
                        startTime = System.currentTimeMillis();
                    }
                    long now = System.currentTimeMillis();
                    long duration = now - startTime;
                    double t = (double) duration / (double) playTime;
                    if (duration >= playTime) {
                        t = 1;
                    }

                    double progress = splineInterpolator.interpolate(t);

                    x = startAt + ((int) Math.round((endAt - startAt) * progress));
                    repaint();
                }
            });
            timer.setInitialDelay(0);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if (!timer.isRunning()) {
                        startTime = -1;
                        startAt = 0;
                        endAt = getWidth() - 10;
                        timer.start();
                    }
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            g2d.fillRect(x, (getHeight() / 2) - 5, 10, 10);
            g2d.dispose();
        }

    }

    public static class SplineInterpolator {

        private final double points[];
        private final List<PointUnit> normalisedCurve;

        public SplineInterpolator(double x1, double y1, double x2, double y2) {
            points = new double[]{x1, y1, x2, y2};

            final List<Double> baseLengths = new ArrayList<>();
            double prevX = 0;
            double prevY = 0;
            double cumulativeLength = 0;
            for (double t = 0

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...