我的动画圆圈为什么位置会闪烁?

huangapple go评论119阅读模式
英文:

Why does the locaton of my animated circles flicker?

问题

使用trident,我创建了一个(表面上)简单的动画。一些圆圈从底部向顶部移动,然后再通过正弦插值返回:

我的动画圆圈为什么位置会闪烁?

动画本身似乎运行正常,但是有一两帧,所有的球都会在最高位置闪烁。

为什么会闪烁?是谁用似乎错误的值调用了setY方法?

我创建了一个测试类来重现这种行为。你需要radiance-trident 3.0来使其工作:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JPanel;

import org.pushingpixels.trident.api.Timeline;
import org.pushingpixels.trident.api.Timeline.RepeatBehavior;
import org.pushingpixels.trident.api.ease.TimelineEase;
import org.pushingpixels.trident.api.swing.SwingRepaintTimeline;

public class MovingSpheresTest extends JDialog {

    // ...(代码继续)

    private class FullSine implements TimelineEase {

        // ...(代码继续)
        
        @Override
        public float map(float durationFraction) {
            return ((float) Math.sin(durationFraction * Math.PI * 2f + horizontalOffset) + 1f) / 2f;
        }
    }

    private void installRepaintTimeline() {
        repaintTimeline = SwingRepaintTimeline.repaintBuilder(contentPanel).build();
        repaintTimeline.playLoop(RepeatBehavior.LOOP);
    }

    public class CenteredSphere extends Ellipse2D.Double {

        // ...(代码继续)

        public void setY(double y) {
            setFrameFromCenter(sphereCenterX, y, sphereCenterX + sphereRadius, y + sphereRadius);
        }
    }
}
英文:

With trident I created a (seemingly) simple animation. A number of circles are moving from bottom to top and back again with a sine interpolation:

我的动画圆圈为什么位置会闪烁?

The animation itself seems to work, but there is one or two frames where all my spheres flicker all to the topmost location.

Why does it flicker? Who is invoking the setY method with seemingly wrong values?

I've made a testclass to reproduce the behavior. You need radiance-trident 3.0 to make it work:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JPanel;

import org.pushingpixels.trident.api.Timeline;
import org.pushingpixels.trident.api.Timeline.RepeatBehavior;
import org.pushingpixels.trident.api.ease.TimelineEase;
import org.pushingpixels.trident.api.swing.SwingRepaintTimeline;

public class MovingSpheresTest extends JDialog {

	private final double sphereRadius = 3d;

	private final double sphereCount = 12d;

	private final double helixHeight = 100d;

	private final double size = 200d;

	private final double animationSpeed = 0.5d;

	private List<CenteredSphere> spheres;

	private SwingRepaintTimeline repaintTimeline;

	private final JPanel contentPanel = new JPanel() {
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
			paintFrame((Graphics2D) g);
		}

		private void paintFrame(Graphics2D g) {
			Graphics2D create = (Graphics2D) g.create();
			try {
				create.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				create.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				create.setColor(Color.BLACK);
				for (CenteredSphere centeredSphere : spheres) {
					create.fill(centeredSphere);
				}
			} finally {
				create.dispose();
			}
		}
	};

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		try {
			MovingSpheresTest dialog = new MovingSpheresTest();
			dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			dialog.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Create the dialog.
	 */
	public MovingSpheresTest() {
		setBounds(100, 100, 450, 300);
		getContentPane().setLayout(new BorderLayout());
		contentPanel.setLayout(new FlowLayout());
		getContentPane().add(contentPanel, BorderLayout.CENTER);
		installSpheres();
		installRepaintTimeline();
	}

	private void installSpheres() {
		double helixRadius = helixHeight / 2;
		double effectiveWidth = size - (2 * sphereRadius);
		double sphereDistance = effectiveWidth / (sphereCount - 1);
		double sphereCenterX = sphereRadius;
		double sphereCenterY = size / 2d;
		double sphereCenterYInitial = sphereCenterY - helixRadius;

		spheres = new ArrayList<>();
		for (int sphereIndex = 0; sphereIndex < sphereCount; sphereIndex++) {
			CenteredSphere sphere = new CenteredSphere(sphereCenterX, sphereRadius);
			spheres.add(sphere);
			sphereCenterX += sphereDistance;
			Timeline.builder()
					.addPropertyToInterpolate(Timeline.<Double>property("y").on(sphere).from(sphereCenterYInitial)
							.to(sphereCenterY + helixRadius))
					.setEase(new FullSine((float) (sphereIndex * 2 * Math.PI / sphereCount)))
					.setDuration((long) (animationSpeed * 3000d)).playLoop(RepeatBehavior.LOOP);
		}

	}

	private class FullSine implements TimelineEase {

		private float horizontalOffset;

		private FullSine(float horizontalOffset) {
			this.horizontalOffset = horizontalOffset;
		}

		@Override
		public float map(float durationFraction) {
			return ((float) Math.sin(durationFraction * Math.PI * 2f + horizontalOffset) + 1f) / 2f;
		}
	}

	private void installRepaintTimeline() {
		repaintTimeline = SwingRepaintTimeline.repaintBuilder(contentPanel).build();
		repaintTimeline.playLoop(RepeatBehavior.LOOP);
	}

	public class CenteredSphere extends Ellipse2D.Double {

		private double sphereCenterX;
		private double sphereRadius;

		public CenteredSphere(double sphereCenterX, double sphereRadius) {
			this.sphereCenterX = sphereCenterX;
			this.sphereRadius = sphereRadius;
		}

		public void setY(double y) {
			setFrameFromCenter(sphereCenterX, y, sphereCenterX + sphereRadius, y + sphereRadius);
		}
	}
}

答案1

得分: 2

此处所指出并在此处修复,

> 这很有趣。这是因为基本假设是TimelineEase始终在不对区间[0.0-1.0]的端点进行“扭曲”的情况下进行映射。在这种特殊情况下,在每次动画循环期间,自定义的FullSine被用于根据球体偏移重新映射该区间,但在循环重置期间,“结束”点未被映射。

英文:

As noted here and fixed here,
>This is interesting. It's because of the underlying assumption that the TimelineEase always maps the [0.0-1.0] interval without "warping" the end points. In this particular case, during each animation loop, the custom FullSine is used to remap that interval based on the sphere offset, but during the loop reset, the "end" points are not mapped.

huangapple
  • 本文由 发表于 2020年9月30日 05:40:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/64128020.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定