-1

I've created a very simple form of a Cubic Bézier in Java in order to retrieve the Y value of the point retrieved given the time (t). In my implementation My first (P1) and last (P4) points are always (0, 0) and (1, 1) respectively, and I'm only going to be manipulating P1 and P2. The purpose of this is to create a modifiable curve used to retrieve a value that will be used to multiply and manipulate other values, like a difficulty ramp in video games.

I've tested my implementation using P2=(0.1, 0.1) and P3=(0.9,0.9), so the curve should effectively be a straight line, and whatever my input value (t) is, the output should mimic:

Cubic Bézier with P2=(0.1, 0.1) and P3=(0.9,0.9)

Here is my CubicBezier class:

@AllArgsConstructor
public class CubicBezier {

    private static final Point P1 = new Point(0, 0);
    private static final Point P4 = new Point(1, 1);

    private Point p2;
    private Point p3;

    public double getValue(double t) {
        double dt = 1d - t;
        double dt2 = dt*dt;
        double t2 = t*t;

        Point temp = p2.copy();

        return P1.copy()
                 .scale(dt2 * dt)
                 .add(temp.scale(3 * dt2 * t))
                 .add(temp.set(p3).scale(3 * dt * t2))
                 .add(temp.set(P4).scale(t2 * t))
                 .getY();
    }
}

And my Point class:

@Data
@AllArgsConstructor
public class Point {
    private double x;
    private double y;

    public Point(Point point) {
        this.x = point.x;
        this.y = point.y;
    }

    public Point copy() {
        return new Point(this);
    }

    public Point set(Point point) {
        this.x = point.x;
        this.y = point.y;
        return this;
    }

    public Point add(double scalar) {
        this.x += scalar;
        this.y += scalar;
        return this;
    }

    public Point add(double x, double y) {
        this.x += x;
        this.y += y;
        return this;
    }

    public Point add(Point point) {
        return add(point.x, point.y);
    }

    public Point scale(double scalar) {
        this.x *= scalar;
        this.y *= scalar;
        return this;
    }

    public Point scale(double x, double y) {
        this.x *= x;
        this.y *= y;
        return this;
    }

    public Point scale(Point point) {
        return scale(point.x, point.y);
    }

}

My main method:

public static void main(String[] args) {
    CubicBezier bezier = new CubicBezier(new Point(0.1d, 0.1d), new Point(0.9d, 0.9d));

    double value = 0.4;
    System.out.println(value + ": " + bezier.getValue(value));
}

Expected output should be:

0.4: 0.4

However, the output I receive is:

0.4: 0.36640000000000006

And I can't understand why. My getValue method is modelled using the Cubic Bézier explicit form specified on Wikipedia. Am I missing something?

Note: I'm using Lombok to remove some boilerplate. I can specify this boilerplate if necessary.

EDIT:

So it appears that my bezier curve is actually working as it should, and that I've been mistaking t as a value on the x axis, assuming that the y value of the calculated bezier curve will be in relation to the x axis. The functionality I want is that, with the resulting curve, given a value x, return the y value in relation. So in the screenshot above, where the curve is a straight line, x should equal y.

driima
  • 623
  • 1
  • 11
  • 28
  • See https://stackoverflow.com/questions/322749/retain-precision-with-double-in-java – PM 77-1 Jan 16 '20 at 22:03
  • Does that explain the huge loss of 0.0336 I'm experiencing in my actual output? – driima Jan 16 '20 at 22:07
  • Not necessarily. – PM 77-1 Jan 16 '20 at 22:13
  • I see where p2 is declared, but I don't see where is it given a value prior to calling p2.copy(); – FredK Jan 16 '20 at 22:28
  • That's lombok's @AllArgsConstructor. See `new CubicBezier(new Point(0.1d, 0.1d), new Point(0.9d, 0.9d))` where p2 = `(0.1d, 0.1d)` – driima Jan 16 '20 at 23:24
  • There's nothing wrong with your code. There's something wrong with your assumption: you're asking for the `y` value at `t=0.4`. That is **not** the same as the `y` value at `x=0.4` at all. The values you're seeing are 100% correct: at `t=0.4`, both `x` and `y` are 0.36664 – Mike 'Pomax' Kamermans Jan 19 '20 at 20:03

1 Answers1

1

I figured out the answer to my question. There was never anything wrong with my code, the error was a lack of understanding. I had assumed that because P1, p2, p3 and P4 were all points that lay on a straight line between 0 and 1 on both the x and y axes, that no matter where they were positioned on that line, time would reflect progression identically, i.e. x would equal y.

However, because p2 and p3 are closer to P0 and P4 respectively, the progression along the curve is stretched out relative to the time.

In order for a Cubic Bézier curve to have the same progression (y) value as the time (t) value where P1 = (0, 0) and P4 = (1, 1), both inside points must be spread evenly along the curve on both axes. So p2 must be (0.33333, 0.33333) and p3 must be (0.66666, 0.66666).

An example of this problem is shown in this video here. When the points are spread apart, the resulting value is affected.

driima
  • 623
  • 1
  • 11
  • 28
  • And you're still not there: the single `t` value _defines both_ the `x` and `y` values. P2 and P3 don't have to be at 1/3 and at 2/3 at all, but what _does_ need to happen if you don't pick those exact intervals is that if you have an `x` value, you need to find the `t` value that _yields_ that `x`, then use that found `t` value to calculate the associated `y`: https://pomax.github.io/bezierinfo/#yforx – Mike 'Pomax' Kamermans Jan 19 '20 at 20:05
  • That is a golden piece of information that I was not able to find. Thank you! – driima Jan 22 '20 at 01:38