Вот моя попытка нарисовать кубическую кривую Безье и получить значение при увеличении 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).
Все еще неправильный результат.
Где я ошибаюсь в кубическом расчете?
Вы используете 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}")
Я действительно думаю, что posX не может быть линейным (en.wikipedia.org/wiki/B%C3%A9zier_curve). Переменную надо делать через t. Кстати, вы можете разрешить x как результат квадратичной функции
Извините, я не понимаю сути. Допустим, у меня есть ползунок от 0 до 200. Как мне получить правильное значение t в этом диапазоне, поскольку это не (как вы сказали) слайдерValue/200?
Что я думаю в основном: как вы вычислили 0,47 из 170? :)
Я редактирую свой ответ, чтобы объяснить свой метод определения x
Не вижу правок :(
Сделанный ! @markzzz
Давайте продолжим обсуждение в чате.
Привет чувак, есть новости? :)
@markzzz Я ответил в чате, вот мое решение: jsfiddle.net/v94ngw6p/3 Можете ли вы сказать мне, что все в порядке?
Спасибо за ответ :) Это приближение, да? Я попытался, например, разместить 0,25, что должно быть posX 200 пикселей и высотой 180 пикселей, но там написано 148,32 пикселей... . Невозможно получить правильное/реальное значение?
Благодаря тому, как мы нашли t
, вы можете иметь только приблизительное представление. Но вы можете повысить точность, увеличив значение tolerance
. Пример: tolerance = 1e-14
==> Y=179,98 Здесь: jsfiddle.net/82u7qg6b
Я проверю дальше и дам вам знать :) Спасибо на данный момент
Да, я хочу, чтобы posX был линейным. Например, 0,25 — 200 пикселей, 0,5 — 400 пикселей и 1 — 800 пикселей. как я могу заставить getYByT справиться с этим?