Что не так в этом вычислении кубической кривой Безье?

Вот моя попытка нарисовать кубическую кривую Безье и получить значение при увеличении 170x (учитывая P1 и P2):

<canvas id = "myCanvas" width = "800" height = "200" style = "border: 1px solid black;"></canvas>
<div id = "result" style = "margin-top: 20px;">N/A</div>

<script>
    var canvas = document.getElementById('myCanvas');
    var ctx = canvas.getContext('2d');
    var resultDiv = document.getElementById('result');

    // Function to flip the y-coordinate
    function flipY(y) {
        return canvas.height - y;
    }

    // Class to represent a single Bézier curve block
    class BezierBlock {
        constructor(P0, P1, P2, P3) {
            this.P0 = { x: P0.x, y: flipY(P0.y) }; // Start point
            this.P1 = { x: P1.x, y: flipY(P1.y) }; // First control point
            this.P2 = { x: P2.x, y: flipY(P2.y) }; // Second control point
            this.P3 = { x: P3.x, y: flipY(P3.y) }; // End point

            this.minX = Math.min(this.P0.x, this.P3.x);
            this.maxX = Math.max(this.P0.x, this.P3.x);
        }

        draw() {
            // Draw the cubic Bézier curve
            ctx.setLineDash([]);
            ctx.beginPath();
            ctx.moveTo(this.P0.x, this.P0.y);
            ctx.bezierCurveTo(this.P1.x, this.P1.y, this.P2.x, this.P2.y, this.P3.x, this.P3.y);
            ctx.strokeStyle = 'black';
            ctx.stroke();
            
            // Draw the vertical cursor line at the current slider position
            ctx.setLineDash([5, 5]);
            ctx.beginPath();
            ctx.moveTo(currentX, 0);
            ctx.lineTo(currentX, canvas.height);
            ctx.strokeStyle = 'blue';
            ctx.stroke();
            ctx.setLineDash([]);

            // Draw the control points
            ctx.fillStyle = 'red';
            ctx.fillRect(this.P0.x - 3, this.P0.y - 3, 6, 6);  // P0
            ctx.fillStyle = 'blue';
            ctx.fillRect(this.P1.x - 3, this.P1.y - 3, 6, 6);  // P1
            ctx.fillStyle = 'blue';
            ctx.fillRect(this.P2.x - 3, this.P2.y - 3, 6, 6);  // P2
            ctx.fillStyle = 'red';
            ctx.fillRect(this.P3.x - 3, this.P3.y - 3, 6, 6);  // P3
        }

        // Method to calculate the y value on the curve at a given x position
        getYByX(posX) {
            let t = (posX - this.P0.x) / (this.P3.x - this.P0.x);
            if (t < 0 || t > 1) return null;  // posX is out of bounds for this curve

            let y =
                Math.pow(1 - t, 3) * this.P0.y +
                3 * Math.pow(1 - t, 2) * t * this.P1.y +
                3 * (1 - t) * Math.pow(t, 2) * this.P2.y +
                Math.pow(t, 3) * this.P3.y;

            return flipY(y);
        }
    }

    // Define the points for each block
    const blocks = [
        new BezierBlock({x: 0, y: 0}, {x: 200, y: 0}, {x: 200, y: 0}, {x: 200, y: 180})
    ];

    // Draw all the Bezier blocks
    function drawAll() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        blocks.forEach(block => block.draw());
    }

    // Function to process a given x position and display the corresponding y value
    function process(posX) {
        for (let block of blocks) {
            if (posX >= block.P0.x && posX <= block.P3.x) {
                let posY = block.getYByX(posX);
                if (posY !== null) {
                    resultDiv.innerText = `X=${posX}: Y=${posY.toFixed(2)}`;
                    currentX = posX;
                    return;
                }
            }
        }
    }

    let currentX = 170; // Initialize currentX for the cursor
    drawAll();
    process(currentX); // Process initial position

</script>

Что отображается как:

Но значение getYByX должно быть примерно 20 (что касается графика), а не 110. Обратите внимание, что значения Y перевернуты (поэтому нижнее значение равно 0, верхнее — 200).

Все еще неправильный результат.

Где я ошибаюсь в кубическом расчете?

🤔 А знаете ли вы, что...
JavaScript обеспечивает обработку ошибок с использованием конструкции try...catch.


72
1

Ответ:

Решено

Вы используете X как линейное значение, а не как кубическую развивающуюся переменную. Я взял ваш код и получил X благодаря линейному t (та же формула, что и у вашего Y, но с осью X). Я думаю, что это работает с моим методом:

<canvas id = "myCanvas" width = "800" height = "200" style = "border: 1px solid black;"></canvas>
<div id = "result" style = "margin-top: 20px;">N/A</div>

<script>
    var canvas = document.getElementById('myCanvas');
    var ctx = canvas.getContext('2d');
    var resultDiv = document.getElementById('result');

    // Function to flip the y-coordinate
    function flipY(y) {
        return canvas.height - y;
    }

    // Class to represent a single Bézier curve block
    class BezierBlock {
        constructor(P0, P1, P2, P3) {
            this.P0 = { x: P0.x, y: flipY(P0.y) }; // Start point
            this.P1 = { x: P1.x, y: flipY(P1.y) }; // First control point
            this.P2 = { x: P2.x, y: flipY(P2.y) }; // Second control point
            this.P3 = { x: P3.x, y: flipY(P3.y) }; // End point

            this.minX = Math.min(this.P0.x, this.P3.x);
            this.maxX = Math.max(this.P0.x, this.P3.x);
        }

        draw() {
            // Draw the cubic Bézier curve
            ctx.setLineDash([]);
            ctx.beginPath();
            ctx.moveTo(this.P0.x, this.P0.y);
            ctx.bezierCurveTo(this.P1.x, this.P1.y, this.P2.x, this.P2.y, this.P3.x, this.P3.y);
            ctx.strokeStyle = 'black';
            ctx.stroke();
            
            // Draw the vertical cursor line at the current slider position
            ctx.setLineDash([5, 5]);
            ctx.beginPath();
            ctx.moveTo(t, 0);
            ctx.lineTo(t, canvas.height);
            ctx.strokeStyle = 'blue';
            ctx.stroke();
            ctx.setLineDash([]);

            // Draw the control points
            ctx.fillStyle = 'red';
            ctx.fillRect(this.P0.x - 3, this.P0.y - 3, 6, 6);  // P0
            ctx.fillStyle = 'blue';
            ctx.fillRect(this.P1.x - 3, this.P1.y - 3, 6, 6);  // P1
            ctx.fillStyle = 'blue';
            ctx.fillRect(this.P2.x - 3, this.P2.y - 3, 6, 6);  // P2
            ctx.fillStyle = 'red';
            ctx.fillRect(this.P3.x - 3, this.P3.y - 3, 6, 6);  // P3
        }

        // Method to calculate the y value on the curve at a given x position
        getYByT(t) {
            let y =
                Math.pow(1 - t, 3) * this.P0.y +
                3 * Math.pow(1 - t, 2) * t * this.P1.y +
                3 * (1 - t) * Math.pow(t, 2) * this.P2.y +
                Math.pow(t, 3) * this.P3.y;

            return flipY(y);
        }
        getXByT(t) {
            let x =
                Math.pow(1 - t, 3) * this.P0.x +
                3 * Math.pow(1 - t, 2) * t * this.P1.x +
                3 * (1 - t) * Math.pow(t, 2) * this.P2.x +
                Math.pow(t, 3) * this.P3.x;

            return x;
        }
    }

    // Define the points for each block
    const blocks = [
        new BezierBlock({x: 0, y: 0}, {x: 200, y: 0}, {x: 200, y: 0}, {x: 200, y: 180})
    ];

    // Draw all the Bezier blocks
    function drawAll() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        blocks.forEach(block => block.draw());
    }

    // Function to process a given x position and display the corresponding y value
    function process(t) {
        for (let block of blocks) {
            if (t >= block.P0.x && t <= block.P3.x) {
                let posY = block.getYByT(t);
                let posX = block.getXByT(t);
                if (posY !== null) {
                    resultDiv.innerText = `X=${posX.toFixed(2)}: Y=${posY.toFixed(2)}`;
                    t = posX;
                    return;
                }
            }
        }
    }

    let t = 0.47 // Average value of t for x ~= 170
    drawAll();
    process(t); // Process initial position

</script>


Обновлено: я нашел значение x с помощью скрипта Python и путем решения уравнения кубической кривой Безье.

Исходное уравнение:

Расширенная форма:

Эта формула представляет собой полиномиальное уравнение:

с

В модуле scipy есть функция fsolve, которая может решать некоторые уравнения, подобные этому.

from scipy.optimize import fsolve

x0, x1, x2, x3 = 0, 200, 200, 200  # Your x values

a: int = -x0 + 3 * x1 - 3 * x2 + x3
b: int = 3 * x0 - 6 * x1 + 3 * x2
c: int = -3 * x0 + 3 * x1
d: int = x0


def bezier_x(t: float):
    return a * t ** 3 + b * t ** 2 + c * t + d


def solve_for_t(expected: float):
    delta = lambda t: bezier_x(t) - expected
    t_initial = 0.5
    t_solution = fsolve(delta, t_initial)
    return t_solution[0]


x_target: float = 170.
t_result = solve_for_t(x_target)
print(f"The parameter t corresponding to x = {x_target} is approximately {t_result:.4f}")