I have a 2 points projection sketch based on two lines intersection algorithms.
For example, if there's a 3D point { x: -4, y: 2, z: -2 } and axes origin is pre-defined, I could find a = {x: -4, y: 2, z: 0} and b = {x: 0, y: 2, z: -2} points and find one possible intersection of line {vanishingPointX, a) and another line of {vanishingPointZ, b) and as you can see the code works fine.
And the task is to add another 3-rd point, so the output should like this:
it's a rough illustration, some lines are distorted.
I have tried to project Y values along X-axis, but anyway neither there's no algorithm of three lines intersection nor these three lines don't intersect together at one point.
And the last but not least, I am aware that this problem could be solved with matrices, however, I'm trying to do it calculus-free.
const scale = 64.0;
const far = 6.0;
const cube = [
{ x: 1.0, y: 1.0, z: 1.0 },
{ x: 1.0, y: -1.0, z: 1.0 },
{ x: -1.0, y: -1.0, z: 1.0 },
{ x: -1.0, y: 1.0, z: 1.0 },
{ x: 1.0, y: 1.0, z: -1.0 },
{ x: 1.0, y: -1.0, z: -1.0 },
{ x: -1.0, y: -1.0, z: -1.0 },
{ x: -1.0, y: 1.0, z: -1.0 },
];
const sides = [0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7];
let vanishingPointZ = { x: -3.5, y: 2.0 };
let vanishingPointX = { x: 5.0, y: 2.0 };
let vanishingPointY = { x: 0.0, y: -6.0 };
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let zero = { x: canvas.width / 2, y: canvas.height / 2 };
draw();
function draw(){
ctx.font = '32px serif';
ctx.fillStyle = "#0F0"
ctx.fillText('X', zero.x + vanishingPointX.x * scale + 16, zero.y + vanishingPointX.y * scale + 16);
ctx.fillStyle = "#F0F";
ctx.fillText('Y', zero.x + vanishingPointY.x * scale + 32, zero.y + vanishingPointY.y * scale + 16);
ctx.fillStyle = "#00F";
ctx.fillText('Z', zero.x + vanishingPointZ.x * scale - 32, zero.y + vanishingPointZ.y * scale + 16);
cube.forEach((p_, i_) =>{
project(p_);
let pos = { x: zero.x + p_.dx * scale, y: zero.y + p_.dy * scale };
//to x
ctx.beginPath();
ctx.moveTo(zero.x + vanishingPointX.x * scale, zero.y + vanishingPointX.y * scale);
ctx.lineTo(pos.x, pos.y);
ctx.closePath();
ctx.strokeStyle = "rgba(0, 255, 0, 0.33)";
ctx.stroke();
//to z
ctx.beginPath();
ctx.moveTo(zero.x + vanishingPointZ.x * scale, zero.y + vanishingPointZ.y * scale);
ctx.lineTo(pos.x, pos.y);
ctx.closePath();
ctx.strokeStyle = "rgba(0, 0, 255, 0.33)";
ctx.stroke();
//to upper y
//to x
ctx.beginPath();
ctx.moveTo(zero.x + vanishingPointY.x * scale, zero.y + vanishingPointY.y * scale);
ctx.lineTo(pos.x, pos.y);
ctx.closePath();
ctx.strokeStyle = "rgba(255, 0, 255, 0.33)";
ctx.stroke();
ctx.beginPath();
ctx.arc(pos.x, pos.y, 8, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "#DEDEDE";
ctx.fill();
})
for(let i = 0; i < sides.length; i += 2){
ctx.beginPath();
ctx.moveTo(zero.x + cube[sides[i]].dx * scale, zero.y + cube[sides[i]].dy * scale);
ctx.lineTo(zero.x + cube[sides[i + 1]].dx * scale, zero.y + cube[sides[i + 1]].dy * scale);
ctx.closePath();
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
function project(p_){
let distX = Math.sqrt(Math.pow(vanishingPointX.x, 2), Math.pow(vanishingPointX.y - p_.y, 2));
let vx = { x: vanishingPointX.x, y: vanishingPointX.y - p_.y };
let nx = Math.exp( p_.x / far );
vx.x *= nx;
vx.y *= nx;
let x = { x: vanishingPointX.x - vx.x, y: vanishingPointX.y - vx.y };
let distZ = Math.sqrt(Math.pow(vanishingPointZ.x, 2), Math.pow(vanishingPointZ.y - p_.y, 2));
let vz = { x: vanishingPointZ.x, y: vanishingPointZ.y - p_.y };
let nz = Math.exp( p_.z / far );
vz.x *= nz;
vz.y *= nz;
let z = { x: vanishingPointZ.x - vz.x, y: vanishingPointZ.y - vz.y };
let out = twoLinesIntersection(vanishingPointZ, z, vanishingPointX, x);
//trying to calculate y projection and it seems that as standalone it work fine
let distY = Math.sqrt(Math.pow(vanishingPointY.x, 2), Math.pow(vanishingPointX.y - p_.x, 2));
let vy = { x: vanishingPointY.y, y: vanishingPointY.y - p_.x };
let ny = Math.exp( p_.y / far );
vy.x *= ny;
vy.y *= ny;
let y = { x: vanishingPointY.x - vy.x, y: vanishingPointY.y - vy.y };
p_.dx = out.x;
p_.dy = out.y;
}
function twoLinesIntersection(p1_, p4_, p3_, p2_){
let d1 = (p1_.x - p2_.x) * (p3_.y - p4_.y);
let d2 = (p1_.y - p2_.y) * (p3_.x - p4_.x);
let d = (d1) - (d2);
let u1 = (p1_.x * p2_.y - p1_.y * p2_.x);
let u4 = (p3_.x * p4_.y - p3_.y * p4_.x);
let u2x = p3_.x - p4_.x;
let u3x = p1_.x - p2_.x;
let u2y = p3_.y - p4_.y;
let u3y = p1_.y - p2_.y;
return { x: (u1 * u2x - u3x * u4) / d, y: (u1 * u2y - u3y * u4) / d };
}
body { margin: 0; }
<canvas id='canvas' width='800' height='800'></canvas>
We can make this easier to compute by, rather than adding vector compounds to a start coordinate until we're at the "real" coordinate, treating the problem as one of line intersections. For example, if we want the point XYZ = (1,2,2) we can find it using line intersections by following this visual "recipe":
For any 3D point we can first compute the points on the YZ and XY planes, after which we can find the true point with one more line intersection. This does rely on knowing where on the screen our origin can be found (we could pick a point in the middle of the triangle Z-Y-X, but whatever screen point we pick, let's call that C, after which the code looks like this:
function get(x, y, z) {
if (x===0 && y===0 && z===0) return C;
// Get the points at the distances along our Z and X axes:
const px = lerp(C, X, perspectiveMap(x));
const pz = lerp(C, Z, perspectiveMap(z));
// If our 3D coordinate lies on the XZ plane, then this
// is just a matter of finding the line/line intersection:
if (y==0) return twoLinesIntersection(X, pz, Z, px);
// If it's not, we construct the two points on the YZ and XY planes:
const py = lerp(C, Y, perspectiveMap(y));
const YZ = twoLinesIntersection(Y, pz, Z, py);
const XY = twoLinesIntersection(Y, px, X, py);
// And then the 3D coordinate is a line/line intersection with those.
return twoLinesIntersection(XY, Z, X, YZ);
}
function lerp(v1, v2, r) {
const mr = 1 - r;
return {
x: v1.x * mr + v2.x * r,
y: v1.y * mr + v2.y * r,
z: v1.z * mr + v2.z * r,
};
}
With the perspective mapping function turns a value on the axis into a distance ratio between the origin and the axis' vanishing point. What that function looks like is effectively up to you, but if we want straight lines to stay straight, we'll want a rational perspective function:
const DEFAULT_PERSPECTIVE_STRENGTH = 0.3;
function perspectiveMap(value, strength=DEFAULT_PERSPECTIVE_STRENGTH) {
return 1 - 1/(1 + value * strength);
}
(we computer "1 minus..." because as a ratio between the origin and a vanishing point, we want this value to be 0 at x/y/z=0, so that lerp(origin, vanishingPoint, value) yields the origin, and we want it to become 1 as x/y/z approach infinity. Just on its own, 1/(1+v*s) does the exact opposite of that, so subtracting it from one "flips" it around)
Implemented as a snippet:
const w = 600, h = 300;
perspective.width = w;
perspective.height = h;
const DEFAULT_PERSPECTIVE_STRENGTH = 0.2;
const ctx = perspective.getContext("2d");
// our vanishing points and origin
let Z = { x: w*0.05, y: h*0.85 };
let X = { x: w*0.92, y: h*0.95 };
let Y = { x: w*0.65, y: h*0.1 };
let C = { x: w*0.65, y: h*0.50 };
// draw our "grid" and labels
line(X,C);
line(Y,C);
line(Z,C);
ctx.strokeStyle = `#00000020`;
drawGrid(10);
ctx.fillStyle = `#400`;
text("X", X, 10, 0);
text("Z", Z, -10, 0);
text("Y", Y, 0, -5);
// draw a 2x2x2 cube
ctx.strokeStyle = `#000000`;
drawCube(2);
// ------------ functions for content ------------
function drawGrid(e) {
for(let i=0; i<e; i++) {
line(X, get(0,i,0));
line(X, get(0,0,i));
line(Y, get(i,0,0));
line(Y, get(0,0,i));
line(Z, get(i,0,0));
line(Z, get(0,i,0));
}
}
function drawCube(n) {
const cube = getCube(n);
const [p1,p2,p3,p4,p5,p6,p7,p8] = cube.map(p => get(p));
quad(p1,p2,p3,p4);
quad(p5,p6,p7,p8);
line(p1,p5);
line(p2,p6);
line(p3,p7);
line(p4,p8);
}
function getCube(n) {
return [
{x: n, y: n, z: n},
{x: 0, y: n, z: n},
{x: 0, y: n, z: 0},
{x: n, y: n, z: 0},
{x: n, y: 0, z: n},
{x: 0, y: 0, z: n},
{x: 0, y: 0, z: 0},
{x: n, y: 0, z: 0},
];
}
// ------------ some draw util functions ------------
function line(p1, p2) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
function quad(p1, p2, p3, p4) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.lineTo(p3.x, p3.y);
ctx.lineTo(p4.x, p4.y);
ctx.closePath();
ctx.stroke();
}
function text(str, point, ox=0, oy=0) {
const { x, y } = point;
ctx.fillText(str, x+ox, y+oy);
}
// ------------ and: our coordinate computer ------------
function get(x,y,z) {
if (y === undefined) {
z = x.z;
y = x.y;
x = x.x
}
if (x===0 && y===0 && z===0) return C;
const px = lerp(C, X, map(x));
const pz = lerp(C, Z, map(z));
if (y==0) return lli(X, pz, Z, px);
const py = lerp(C, Y, map(y));
const YZ = lli(Y, pz, Z, py);
const XY = lli(Y, px, X, py);
return lli(XY, Z, X, YZ);
}
function lerp(v1, v2, r) {
const mr = 1 - r;
return {
x: v1.x * mr + v2.x * r,
y: v1.y * mr + v2.y * r,
z: v1.z * mr + v2.z * r
}
}
function lli(p1,p2,p3,p4) {
return lli8(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y,p4.x,p4.y);
}
function lli8(x1,y1,x2,y2,x3,y3,x4,y4) {
const d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
if (d === 0) return undefined;
const f12 = (x1*y2 - y1*x2);
const f34 = (x3*y4 - y3*x4);
const nx = f12*(x3-x4) - (x1-x2)*f34;
const ny = f12*(y3-y4) - (y1-y2)*f34;
return { x: nx/d, y: ny/d };
}
function map(value, strength=DEFAULT_PERSPECTIVE_STRENGTH) {
return 1 - 1/(1 + value*strength);
}
canvas { border: 1px solid black; }
<canvas id="perspective">
Related
I'm given an array of points for a rectangle, sorted from top-left position onwards to each corner clockwise. I need to find the start and end points for a line of each specific corner length from the corner to each side of the rectangle. My code is rotating the points by some random angle just to illustrate the problem but in my actual scenario I only have the corner points and need the start and end points to map out each corner. I'm currently offsetting each point as if the point using directional value array that match to the order of the points in the array when the rectangle isn't rotated, but it doesn't work because the points can be rotated at which point this order also changes. There's an application of sine and cosine that I fail to grasp I'm sure. How can I get the value of each corner point offset by a given length towards each of its sides aligned so as to everything rotates together uniformly?
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
const startPointDirections = [{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 },
{ x: 1, y: 0 }];
const endPointDirections = [{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 }];
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(startPointDirections[i].x * cornerLength + points[i].x, startPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.arc(endPointDirections[i].x * cornerLength + points[i].x, endPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
Seems you need to apply your rotate function to offset points relative to rotated corner, but you have no angle value at this moment. So you can just get direction vectors from rotated sides.
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
let dx = points[i].x - points[(i+3)%4].x //previous index in cyclic manner
let dy = points[i].y - points[(i+3)%4].y
let len = Math.sqrt(dx*dx+dy*dy) //really you know side length so use known value
dx = cornerLength * dx/len //normalized vector multiplied by magnitude
dy = cornerLength * dy/len
startx = points[i].x + dx
starty = points[i].y + dy
endx = points[i].x + dy //here apply rotated by -Pi/2 offset vector
endy = points[i].y - dx
ctx.arc(startx, starty, 3, 0, Math.PI * 2);
ctx.arc(endx, endy, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
(Really dx/len and dy/len are equal to cos(angle) and sin(angle) or vice versa, with different combinations of signs)
I'm trying to identify the following parameters from the example for oval triangle but when i modify the line:
drawCircle(canvas.width / 3,canvas.height / 2,2.5,'red');
//want to replace with specific values but its not working
drawCircle(32 / 3,33 / 2,2.5,'red');
I want to identify the correct parameter so the demo can change the red point into other space inside the triangle
CH4= 20
C2H4= 70
C2H2= 20
Demo:
https://codepen.io/Barak/pen/WwdPxQ
I read the post from stackoverflow community and cannot see values
how to create Duval Triangle in canvas
In the post you've mentioned, markE did a great job replicating the look of a Duval triangle. The only problem is that the codepen including the drawCircle() function is just a dummy and does nothing more than placing a dot at an arbitrary position, given by it's x and y parameters.
To make this function show the correct position of actual data on the triangle - e.g. CH4=20 | C2H4=70 | C2H2=20 - there's a lot more involved.
Let's have a more in-depth look, if we were to solve this graphically on paper.
(The following is based on this paper)
The Duval triangle is basically an equilateral triangle like this, where side b=%CH4,
side a=%C2H4 and side c=%C2H2.
If we consider the following example data %CH4=25 | %C2H4=35 | %C2H2=40
we would have to go from point A to point C, 25% the length of side b and draw a line parallel to side c:
then from point C to point B, 35% the length of side a and draw a line parallel to side b:
and finally from point B to point A, 40% the length of side c and draw a line parallel to side a:
So where those three lines intersect - so the paper says - we have our target position indicating the status. This can be done programatically using plain trigonometry. Well, I'm not too sure why we need all three lines though. As th percentage for CH4 is always parallel to the triangle's base and C2H4 is always parallel to side b, we have an intersection yet and the line for C2H2 is given automatically.
Basically we just need a function which calculates the intersection between the CH4 and the C2H4 line.
I've taken markE's existing code and enhanced it by a function plotResult(), which takes three parameters for the CH4, C2H2 and C2H4 ppm values:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// https://www.researchgate.net/publication/4345236_A_Software_Implementation_of_the_Duval_Triangle_Method
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
var v0 = {
x: 114,
y: 366
};
var v1 = {
x: 306,
y: 30
};
var v2 = {
x: 498,
y: 366
};
var triangle = [v0, v1, v2];
// Define all your segments here
var segments = [{
points: [{
x: 114,
y: 366
}, {
x: 281,
y: 76
}, {
x: 324,
y: 150
}, {
x: 201,
y: 366
}],
fill: 'rgb(172,236,222)',
label: {
text: 'D1',
cx: 200,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 385,
y: 366
}, {
x: 201,
y: 366
}, {
x: 324,
y: 150
}, {
x: 356,
y: 204
}, {
x: 321,
y: 256
}],
fill: 'deepskyblue',
label: {
text: 'D2',
cx: 290,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 297,
y: 46
}, {
x: 392,
y: 214
}, {
x: 372,
y: 248
}, {
x: 441,
y: 366
}, {
x: 385,
y: 366
}, {
x: 321,
y: 256
}, {
x: 356,
y: 204
}, {
x: 281,
y: 76
}],
fill: 'lightCyan',
label: {
text: 'DT',
cx: 370,
cy: 290,
withLine: false,
endX: 366,
endY: 120
},
},
{
points: [{
x: 306,
y: 30
}, {
x: 312,
y: 40
}, {
x: 300,
y: 40
}],
fill: 'black',
label: {
text: 'PD',
cx: 356,
cy: 40,
withLine: true,
endX: 321,
endY: 40
},
},
{
points: [{
x: 312,
y: 40
}, {
x: 348,
y: 103
}, {
x: 337,
y: 115
}, {
x: 297,
y: 46
}, {
x: 300,
y: 40
}],
fill: 'navajoWhite',
label: {
text: 'T1',
cx: 375,
cy: 70,
withLine: true,
endX: 340,
endY: 75
},
},
{
points: [{
x: 348,
y: 103
}, {
x: 402,
y: 199
}, {
x: 392,
y: 214
}, {
x: 337,
y: 115
}],
fill: 'tan',
label: {
text: 'T2',
cx: 400,
cy: 125,
withLine: true,
endX: 366,
endY: 120
},
},
{
points: [{
x: 402,
y: 199
}, {
x: 498,
y: 366
}, {
x: 441,
y: 366
}, {
x: 372,
y: 248
}],
fill: 'peru',
label: {
text: 'T3',
cx: 425,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
// pre-create a canvas-image of the arrowhead
var arrowheadLength = 10;
var arrowheadWidth = 8;
var arrowhead = document.createElement('canvas');
premakeArrowhead();
var legendTexts = ['PD = Partial Discharge', 'T1 = Thermal fault < 300 celcius', '...'];
// start drawing
/////////////////////
// draw colored segments inside triangle
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
// draw ticklines
ticklines(v0, v1, 9, 0, 20);
ticklines(v1, v2, 9, Math.PI * 3 / 4, 20);
ticklines(v2, v0, 9, Math.PI * 5 / 4, 20);
// molecules
moleculeLabel(v0, v1, 100, Math.PI, '% CH4');
moleculeLabel(v1, v2, 100, 0, '% C2H4');
moleculeLabel(v2, v0, 75, Math.PI / 2, '% C2H2');
// draw outer triangle
drawTriangle(triangle);
// draw legend
drawLegend(legendTexts, 10, 10, 12.86);
plotResult(25, 40, 35);
// end drawing
/////////////////////
function drawSegment(s) {
// draw and fill the segment path
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var i = 1; i < s.points.length; i++) {
ctx.lineTo(s.points[i].x, s.points[i].y);
}
ctx.closePath();
ctx.fillStyle = s.fill;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw segment's box label
if (s.label.withLine) {
lineBoxedLabel(s, labelfontsize, labelfontface, labelpadding);
} else {
boxedLabel(s, labelfontsize, labelfontface, labelpadding);
}
}
function moleculeLabel(start, end, offsetLength, angle, text) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = '14px verdana';
var dx = end.x - start.x;
var dy = end.y - start.y;
var x0 = parseInt(start.x + dx * 0.50);
var y0 = parseInt(start.y + dy * 0.50);
var x1 = parseInt(x0 + offsetLength * Math.cos(angle));
var y1 = parseInt(y0 + offsetLength * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(text, x1, y1);
// arrow
var x0 = parseInt(start.x + dx * 0.35);
var y0 = parseInt(start.y + dy * 0.35);
var x1 = parseInt(x0 + 50 * Math.cos(angle));
var y1 = parseInt(y0 + 50 * Math.sin(angle));
var x2 = parseInt(start.x + dx * 0.65);
var y2 = parseInt(start.y + dy * 0.65);
var x3 = parseInt(x2 + 50 * Math.cos(angle));
var y3 = parseInt(y2 + 50 * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x3, y3);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
var angle = Math.atan2(dy, dx);
ctx.translate(x3, y3);
ctx.rotate(angle);
ctx.drawImage(arrowhead, -arrowheadLength, -arrowheadWidth / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function boxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.lineWidth = 1;
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function lineBoxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
var lineToX = s.label.endX;
var lineToY = s.label.endY;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
// the line
ctx.beginPath();
ctx.moveTo(leftX, topY + textheight / 2);
ctx.lineTo(lineToX, topY + textheight / 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
// the boxed text
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function ticklines(start, end, count, angle, length) {
var dx = end.x - start.x;
var dy = end.y - start.y;
ctx.lineWidth = 1;
for (var i = 1; i < count; i++) {
var x0 = parseInt(start.x + dx * i / count);
var y0 = parseInt(start.y + dy * i / count);
var x1 = parseInt(x0 + length * Math.cos(angle));
var y1 = parseInt(y0 + length * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
if (i == 2 || i == 4 || i == 6 || i == 8) {
var labelOffset = length * 3 / 4;
var x1 = parseInt(x0 - labelOffset * Math.cos(angle));
var y1 = parseInt(y0 - labelOffset * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(parseInt(i * 10), x1, y1);
}
}
}
function premakeArrowhead() {
var actx = arrowhead.getContext('2d');
arrowhead.width = arrowheadLength;
arrowhead.height = arrowheadWidth;
actx.beginPath();
actx.moveTo(0, 0);
actx.lineTo(arrowheadLength, arrowheadWidth / 2);
actx.lineTo(0, arrowheadWidth);
actx.closePath();
actx.fillStyle = 'black';
actx.fill();
}
function drawTriangle(t) {
ctx.beginPath();
ctx.moveTo(t[0].x, t[0].y);
ctx.lineTo(t[1].x, t[1].y);
ctx.lineTo(t[2].x, t[2].y);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawLegend(texts, x, y, lineheight) {
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.font = '12px arial';
for (var i = 0; i < texts.length; i++) {
ctx.fillText(texts[i], x, y + i * lineheight);
}
}
function plotResult(val1, val2, val3) {
let deltaX, length;
let sum = val1 + val2 + val3;
const cos60 = Math.cos(Math.PI / 3);
const sin60 = Math.sin(Math.PI / 3);
let ch4 = val1 / sum;
let c2h2 = val2 / sum;
let c2h4 = val3 / sum;
length = Math.sqrt(Math.pow((v1.x - v0.x), 2) + Math.pow((v1.y - v0.y), 2));
let ch4PointA = new Point(v0.x + (length * ch4) * cos60, v0.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v2.x - v1.x), 2) + Math.pow((v2.y - v1.y), 2));
let ch4PointB = new Point(v2.x - (length * ch4) * cos60, v2.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v1.x - v2.x), 2) + Math.pow((v1.y - v2.y), 2));
let c2h4PointA = new Point(v1.x + (length * c2h4) * cos60, v1.y + (length * c2h4) * sin60);
deltaX = (v2.x - v0.x) * c2h4;
let c2h4PointB = new Point(v0.x + deltaX, v0.y);
let point = getIntersection(ch4PointA, ch4PointB, c2h4PointA, c2h4PointB);
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "red";
ctx.fill();
}
function getIntersection(pointA, pointB, pointC, pointD) {
let denominator, a, b, numeratorA, numeratorB;
denominator = ((pointD.y - pointC.y) * (pointB.x - pointA.x)) - ((pointD.x - pointC.x) * (pointB.y - pointA.y));
a = pointA.y - pointC.y;
b = pointA.x - pointC.x;
numeratorA = ((pointD.x - pointC.x) * a) - ((pointD.y - pointC.y) * b);
numeratorB = ((pointB.x - pointA.x) * a) - ((pointB.y - pointA.y) * b);
a = numeratorA / denominator;
b = numeratorB / denominator;
return new Point(pointA.x + (a * (pointB.x - pointA.x)), pointA.y + (a * (pointB.y - pointA.y)));
}
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<canvas id="canvas" width=650 height=500></canvas>
I have a web app, where I am drawing a triangle and also drawing points on top of it to show the vertices that it has. (fig 1 at 0 radians)
The circle and triangle rotate fine, its just the blue point that I cant seem to move appropriately. When I rotate the triangle (along with circle), the X and Y does not translate to either corners of the triangle besides the red dot. (fig 2 at .75 radians)
The shape as a whole is drawn with the following vertices for display points.
this.transform = ctx.getTransform();
this.boundPoints[0] = { //red point
x: (this.transform.a + this.x)+(this.radius)* Math.cos(this.rotation),
y: (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
}
this.boundPoints[1] = { //blue point
x: (this.transform.a + this.x)+(this.radius+ this.range)* Math.cos(this.rotation),
y: (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
}
What I want to have happen, is this
Where that point keeps its position relative to the triangle regardless of its position and rotation in the canvas. Without rotating, I can keep it there with its Y being
y: (this.transform.d + this.y+this.range)
but now I can't rotate or move the shape without the dot losing its placement. (Note: this.rotation is angle in radians)
My way of keeping track of all point of any shape is to create any array in my class that stores those values separate from the actual points that you are drawing to. I use those stored point mainly for collision detection with an odd shape that has been transformed/rotated/scaled.
Without your code it's hard to see how I would implement this technique but here's an example of a rotating triangle that you can scale and transform and the points are always tracked. This example also includes a commented out piece of code showing how to use the centroid to rotate from the center is needed.
this.position is the trasnlate
this.size is scale
this.r is rotate
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
let ptACopy, ptBCopy, ptCCopy;
class Triangle {
constructor(ptA, ptB, ptC) {
this.type = "tri";
this.ptA = ptACopy = ptA;
this.ptB = ptBCopy = ptB;
this.ptC = ptCCopy = ptC;
this.position = { x: 100, y: 100 }; //use this to position
this.size = { x: 2, y: 1 };
this.centroid = {
ox: (this.ptA.x + this.ptB.x + this.ptC.x) / 3,
oy: (this.ptA.y + this.ptB.y + this.ptC.y) / 3
};
this.c = "red";
this.a = 0;
this.r = this.a * (Math.PI / 180);
this.points = [];
for (let i = 0; i < 3; i++) {
this.points.push({ x: 0, y: 0 });
}
}
draw() {
//updates the points to counter the translating of the canvas to the centroid
//this is used to rotate from center if wanted
/*this.ptA = {
x: ptACopy.x - this.centroid.ox,
y: ptACopy.y - this.centroid.oy
};
this.ptB = {
x: ptBCopy.x - this.centroid.ox,
y: ptBCopy.y - this.centroid.oy
};
this.ptC = {
x: ptCCopy.x - this.centroid.ox,
y: ptCCopy.y - this.centroid.oy
};*/
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.setTransform(cos * this.size.x, sin * this.size.x, -sin * this.size.y, cos * this.size.y, this.position.x, this.position.y);
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.lineTo(this.ptA.x, this.ptA.y);
ctx.fill();
ctx.closePath();
ctx.restore();
}
updateCorners() {
this.a += 0.1;
this.r = this.a * (Math.PI / 180);
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
this.points[0].x =
this.ptA.x * this.size.x * cos -
this.ptA.y * this.size.y * sin +
this.position.x;
this.points[0].y =
this.ptA.x * this.size.x * sin +
this.ptA.y * this.size.y * cos +
this.position.y;
this.points[1].x =
this.ptB.x * this.size.x * cos -
this.ptB.y * this.size.y * sin +
this.position.x;
this.points[1].y =
this.ptB.x * this.size.x * sin +
this.ptB.y * this.size.y * cos +
this.position.y;
this.points[2].x =
this.ptC.x * this.size.x * cos -
this.ptC.y * this.size.y * sin +
this.position.x;
this.points[2].y =
this.ptC.x * this.size.x * sin +
this.ptC.y * this.size.y * cos +
this.position.y;
}
drawPoints() {
ctx.fillStyle = "blue";
this.points.map((x) => {
ctx.beginPath();
ctx.arc(x.x, x.y, 3, 0, Math.PI * 2);
ctx.fill();
});
}
}
let triangle = new Triangle(
{ x: 10, y: 20 },
{ x: 50, y: 60 },
{ x: 30, y: 100 }
);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
triangle.draw();
triangle.updateCorners();
triangle.drawPoints();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
To clarify some things in the code:
The purpose of using the following code is to ensure the shape is accurately drawn when translating it.
this.ptA = {
x: ptACopy.x - this.centroid.ox,
y: ptACopy.y - this.centroid.oy
};
If is were to not make a copy of the points and try to do
this.ptA = {
x: ptA.x - this.centroid.ox,
y: ptA.y - this.centroid.oy
};
then I would just get undefined because I'm trying to use ptA to calculate ptA.
Also when creating the triangle if I wanted ptA to be at (0,0) then I could set it there and then use the above function to offset the triangle for rotation purposes. example using that to have it rotate around ptA with a 20px radius:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
let ptACopy, ptBCopy, ptCCopy;
class Triangle {
constructor(ptA, ptB, ptC) {
this.type = "tri";
this.ptA = ptACopy = ptA;
this.ptB = ptBCopy = ptB;
this.ptC = ptCCopy = ptC;
this.position = { x: 100, y: 100 }; //use this to position
this.size = { x: 1, y: 1 };
this.centroid = {
ox: (this.ptA.x + this.ptB.x + this.ptC.x) / 3,
oy: (this.ptA.y + this.ptB.y + this.ptC.y) / 3
};
this.c = "red";
this.a = 0;
this.r = this.a * (Math.PI / 180);
this.points = [];
for (let i = 0; i < 3; i++) {
this.points.push({ x: 0, y: 0 });
}
}
draw() {
//updates the points to counter the translating of the canvas to the centroid
this.ptA = {
x: ptACopy.x + 20,
y: ptACopy.y + 20
};
this.ptB = {
x: ptBCopy.x + 20,
y: ptBCopy.y + 20
};
this.ptC = {
x: ptCCopy.x + 20,
y: ptCCopy.y + 20
};
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.setTransform(cos * this.size.x, sin * this.size.x, -sin * this.size.y, cos * this.size.y, this.position.x, this.position.y);
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.lineTo(this.ptA.x, this.ptA.y);
ctx.fill();
ctx.closePath();
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(0,0,canvas.width, canvas.height);
ctx.restore();
}
updateCorners() {
this.a += 0.5;
this.r = this.a * (Math.PI / 180);
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
this.points[0].x =
this.ptA.x * this.size.x * cos -
this.ptA.y * this.size.y * sin +
this.position.x;
this.points[0].y =
this.ptA.x * this.size.x * sin +
this.ptA.y * this.size.y * cos +
this.position.y;
this.points[1].x =
this.ptB.x * this.size.x * cos -
this.ptB.y * this.size.y * sin +
this.position.x;
this.points[1].y =
this.ptB.x * this.size.x * sin +
this.ptB.y * this.size.y * cos +
this.position.y;
this.points[2].x =
this.ptC.x * this.size.x * cos -
this.ptC.y * this.size.y * sin +
this.position.x;
this.points[2].y =
this.ptC.x * this.size.x * sin +
this.ptC.y * this.size.y * cos +
this.position.y;
}
drawPoints() {
ctx.fillStyle = "blue";
this.points.map((x) => {
ctx.beginPath();
ctx.arc(x.x, x.y, 3, 0, Math.PI * 2);
ctx.fill();
});
}
}
let triangle = new Triangle(
{ x: 0, y: 0 },
{ x: 50, y: 60 },
{ x: 30, y: 100 }
);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
triangle.draw();
triangle.updateCorners();
triangle.drawPoints();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I gave the canvas a background for better visualization.
UPDATE:
I am adding an example using getTransform() which returns the transformation matrix.
We can then use those values to calculate each point by passing them to a function. This shortens the code slightly inside the class and makes things look cleaner IMO.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let t;
class Rect {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.scale = {x: 1, y: 1}
this.cx = this.x + this.w / 2;
this.cy = this.y + this.h / 2;
this.color = "red";
this.angle = 0;
this.rotation = (this.angle * Math.PI) / 180;
this.pts = []
}
draw() {
this.angle += 0.5;
this.rotation = (this.angle * Math.PI) / 180;
const cos = Math.cos(this.rotation)
const sin = Math.sin(this.rotation)
ctx.save();
ctx.setTransform(cos * this.scale.x, sin * this.scale.x, -sin * this.scale.y, cos * this.scale.y, this.x, this.y);
t = ctx.getTransform();
ctx.fillStyle = this.color;
ctx.fillRect(-this.w / 2, -this.h / 2, this.w, this.h);
ctx.restore();
}
drawVertices() {
for (let i=0; i < this.pts.length; i++) {
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(this.pts[i].x, this.pts[i].y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
updateVertices() {
this.pts[0] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], 0, 0, this.cx, this.cy)//top left width and height are passed as 0.
this.pts[1] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], this.w, 0, this.cx, this.cy) //top right only passes width. Height is 0.
this.pts[2] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], this.w, this.h, this.cx, this.cy) //bottom right passes both wodth and height.
this.pts[3] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], 0, this.h, this.cx, this.cy)//bottom left only passes height. Width is 0.
}
}
let rect1 = new Rect(100, 100, 50, 75);
let rect2 = new Rect(250, 150, 100, 25);
function calcVertices(a, b, c, d, e, f, w, h, cx, cy) {
let x, y;
x = (e + w - cx) * a + (f + h - cy) * c + (e);
y = (e + w - cx) * b + (f + h - cy) * d + (f);
return {x: x, y: y}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect1.draw();
rect1.updateVertices();
rect1.drawVertices();
rect2.draw();
rect2.updateVertices();
rect2.drawVertices();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I will answer with complex numbers for convenience, but every complex expression can be rewritten with reals only.
You did not specify the rotation center (presumably the origin) and for generality I will assume the point c. To rotate any point p around this center, we apply the equation
p' = r.(p - c) + c
where r = cos Θ + i sin Θ implements a rotation by angle Θ.
Now to rotate any shape, assuming that the shape has a reference point (say its center or some other remarkable point such as a corner), you need to apply the rotation to this reference point, and assign the angle Θ to the shape (if the shape already had an angle τ assigned to it, the new angle is τ + Θ.)
If you also want to rescale the shape, use r = s (cos Θ + i sin Θ) where s is the desired scaling factor and apply this transformation to the reference point. Also apply the factor s to all dimension parameters of the shapes (or t . s is the parameters already had a factor t applied.)
I am trying to get alert when any of the balls turn red. Every time when the balls turn red, alert should come. Anyone can you help me out to get this thing?
And i want the canvas outline stroke as black color. How to do that? I tried adding > canvas.stokestyle also but it not showing anything.
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
y: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal
};
return ball;
}
var canvas = document.getElementById("Canvas");
var ctx = canvas.getContext("2d");
ctx.globalAlpha = 0.8
var containerR = 150;
canvas.width = canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
getBall(canvas.width / 2, canvas.height - 30, 2, 2, 8, "#32CD32"),
getBall(canvas.width / 3, canvas.height - 50, 2, 2, 8, "#32CD32"),
getBall(canvas.width / 4, canvas.height - 60, 2, 2, 8, "#32CD32"),
getBall(canvas.width / 2, canvas.height / 5, 2, 2, 8, "#32CD32")
];
function drawBall(curBall) {
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2, false);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
}
function updatePos(curBall) {
curBall.x += curBall.dx;
curBall.y += curBall.dy;
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
if (Math.sqrt(dx * dx + dy * dy) >= containerR - curBall.r) {
var v = Math.sqrt(curBall.dx * curBall.dx + curBall.dy * curBall.dy);
var angleToCollisionPoint = Math.atan2(-dy, dx);
var oldAngle = Math.atan2(-curBall.dy, curBall.dx);
var newAngle = 2 * angleToCollisionPoint - oldAngle;
curBall.dx = -v * Math.cos(newAngle);
curBall.dy = v * Math.sin(newAngle);
}
}
function ballsMeet(b1, b2) {
return (Math.hypot(Math.abs(b1.x - b2.x), Math.abs(b1.y - b2.y)) < (b1.r + b2.r))
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
drawBall(balls[i])
updatePos(balls[i])
meet = false
for (var j = 0; j < balls.length; j++) {
if (ballsMeet(balls[i], balls[j]) && i != j) {
meet = true
balls[j].color = "red"
break
}
}
balls[i].color = (meet) ? "red" : "green"
}
requestAnimationFrame(draw);
}
draw();
canvas {
background: #eee;
margin: 0 auto;
}
<html>
<canvas id="Canvas"></canvas>
<div id="x"></div>
<div id="y"></div>
<div id="dx"></div>
<div id="dy"></div>
</html>
Thanks in advance
Don't use alert() when working with RequestAnimationFrame
Don't color balls inside draw (BTW call it engine since it's a RAF recursion), use the drawBall instead.
Given the above, on collision, set the ball property collider to be the colliding ball - and paint accordingly inside drawBall - that way you can restore to the original color once the ball exits collision: ctx.fillStyle = ball.collider ? "red" : ball.color;
You could use CSS radius and box-shadow on the CANVAS element
const ctx = document.getElementById("Canvas").getContext("2d");
const containerR = 90;
const size = containerR * 2
ctx.canvas.width = ctx.canvas.height = size;
ctx.globalAlpha = 0.8
const getBall = (x, y, dx, dy, r, color) => ({x, y, dx, dy, r, color});
const balls = [
getBall(size / 2, size - 30, 1, 1, 30, "Blue"),
getBall(size / 3, size - 50, 1, 1, 20, "Yellow"),
getBall(size / 4, size - 60, 1, 1, 10, "Fuchsia"),
getBall(size / 2, size / 5, 1, 1, 8, "Green"),
];
const drawBall = (ball) => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx.fillStyle = ball.collider ? "red" : ball.color;
ctx.fill();
ctx.closePath();
}
const updatePos = (ball) => {
ball.x += ball.dx;
ball.y += ball.dy;
const dx = ball.x - containerR;
const dy = ball.y - containerR;
if (Math.sqrt(dx * dx + dy * dy) >= containerR - ball.r) {
const v = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
const angleToCollisionPoint = Math.atan2(-dy, dx);
const oldAngle = Math.atan2(-ball.dy, ball.dx);
const newAngle = 2 * angleToCollisionPoint - oldAngle;
ball.dx = -v * Math.cos(newAngle);
ball.dy = v * Math.sin(newAngle);
}
}
const collides = (a, b) => (Math.hypot(Math.abs(a.x - b.x), Math.abs(a.y - b.y)) < (a.r + b.r));
function engine() {
console.clear(); // Clear console test messages
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
balls.forEach((a, ai) => {
a.collider = undefined;
balls.forEach((b, bi) => {
if (bi === ai) return; // Don't look at self
if (collides(a, b)) a.collider = b; // Store the colliding B ball
});
if (a.collider) { // If ball has a collider:
console.log(`${a.color[0]} → ← ${a.collider.color[0]}`);
}
updatePos(a);
drawBall(a);
});
requestAnimationFrame(engine);
}
engine();
canvas {
background: #eee;
margin: 0 auto;
border-radius: 50%;
box-shadow: 0 0 0 4px #000;
}
<canvas id="Canvas"></canvas>
I need to draw and get coordinates of bezier curves of each steps with native Javascript without ctx.bezierCurveTo method.
I found several resources, but I confused. Esspecially this looks pretty close, but I couldnt implemented clearly.
How can I accomplish this?
You can plot out the Bezier:
bezier = function(t, p0, p1, p2, p3){
var cX = 3 * (p1.x - p0.x),
bX = 3 * (p2.x - p1.x) - cX,
aX = p3.x - p0.x - cX - bX;
var cY = 3 * (p1.y - p0.y),
bY = 3 * (p2.y - p1.y) - cY,
aY = p3.y - p0.y - cY - bY;
var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + p0.y;
return {x: x, y: y};
},
(function(){
var accuracy = 0.01, //this'll give the bezier 100 segments
p0 = {x: 10, y: 10}, //use whatever points you want obviously
p1 = {x: 50, y: 100},
p2 = {x: 150, y: 200},
p3 = {x: 200, y: 75},
ctx = document.createElement('canvas').getContext('2d');
ctx.width = 500;
ctx.height = 500;
document.body.appendChild(ctx.canvas);
ctx.moveTo(p0.x, p0.y);
for (var i=0; i<1; i+=accuracy){
var p = bezier(i, p0, p1, p2, p3);
ctx.lineTo(p.x, p.y);
}
ctx.stroke()
})()
Here's a fiddle http://jsfiddle.net/fQYsU/
Here is a code example for any number of points you want to add to make a bezier curve.
Here points you will pass is an array of objects containing x and y values of points.
[ { x: 1,y: 2 } , { x: 3,y: 4} ... ]
function factorial(n) {
if(n<0)
return(-1); /*Wrong value*/
if(n==0)
return(1); /*Terminating condition*/
else
{
return(n*factorial(n-1));
}
}
function nCr(n,r) {
return( factorial(n) / ( factorial(r) * factorial(n-r) ) );
}
function BezierCurve(points) {
let n=points.length;
let curvepoints=[];
for(let u=0; u <= 1 ; u += 0.0001 ){
let p={x:0,y:0};
for(let i=0 ; i<n ; i++){
let B=nCr(n-1,i)*Math.pow((1-u),(n-1)-i)*Math.pow(u,i);
let px=points[i].x*B;
let py=points[i].y*B;
p.x+=px;
p.y+=py;
}
curvepoints.push(p);
}
return curvepoints;
}