Here's my attempt to draw a cubic bezier curve and get the value at 170x (considering P1 and 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>
Which display as:
But the value from getYByX
should be somethings like 20 (as for the plot), not 110. Notice that Y-values are flip (so bottom is 0, top is 200).
Still wrong result.
Where am I wrong on cubic calculation?
You're using X
as a linear value not as cubic evolving variable. I took your code and get an X
thanks to a linear t
(same formula than your Y but with the X axis). I think it's working with my method:
<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>
EDIT: I found the value of x with a python script and by resolving the cubic bezier curve equation.
The original equation:
Expanded form:
This rmula is a polynomial equation as:
with
scipy
module has a function named fsolve that can solve some equation like this one.
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}")