May be someone know good JavaScript library or framework for canvas drawing.
I have following issue:
1) draw some line via free-hand (like Paint/Photoshop pencil)
2) need convert line into "rectangle snake" (build a line using rectangles with fixed width)
look at screenshot
What kind of library will be better to use for this issue?
May be some kind of library alredy has this functionality?
I mean need following functionality:
line approximation
spline separation
converting line to polygons/shapes/objects
Will be great if someone will help me. Thanks!
As you say in your question, your points can be turned into a spline consisting of a set of cubic Bezier curves. Hint: you might simplify the point-set before calculating the spline of the remaining (fewer) points.
Here's how to calculate a set of rectangular-ish polygons along a cubic Bezier curve.
Sidenote: The polygons must be rectangular-ish because it's not possible to create a set of side-connected rectangles along a curved path.
Calculate a set of points along the curve.
Using the points in #1, calculate the tangent angle for each of the points.
Using the points & angles, calculate perpendicular lines extending outward in both directions from each point.
Use the endpoints of the perpendicular lines to build a set of polygons. Each new polygon is built from the prior & current perpendicular endpoints.
Here is annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// variables defining a cubic bezier curve
var PI=Math.PI;
var PI2=PI*2;
var s={x:20,y:30};
var c1={x:300,y:40};
var c2={x:40,y:150};
var e={x:370,y:170};
// an array of points plotted along the bezier curve
var points=[];
// an array of polygons along the bezier curve
var polys=[];
// plot some points & tangent angles along the curve
for(var t=0;t<=100;t+=4){
var T=t/100;
// plot a point on the curve
var pos=getCubicBezierXYatT(s,c1,c2,e,T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
var a = Math.atan2(ty, tx)-PI/2;
// save the x/y position of the point and the tangent angle
points.push({
x:pos.x,
y:pos.y,
angle:a
});
}
// create polygons that extend on either side of the
// original points set
for(var i=1;i<points.length;i++){
var p0=points[i-1];
var p1=points[i];
polys.push({
x0:p0.x+20*Math.cos(p0.angle),
y0:p0.y+20*Math.sin(p0.angle),
x1:p1.x+20*Math.cos(p1.angle),
y1:p1.y+20*Math.sin(p1.angle),
x2:p1.x+20*Math.cos(p1.angle-PI),
y2:p1.y+20*Math.sin(p1.angle-PI),
x3:p0.x+20*Math.cos(p0.angle-PI),
y3:p0.y+20*Math.sin(p0.angle-PI),
});
}
// draw the polygons
for(var i=0;i<polys.length;i++){
var r=polys[i];
ctx.beginPath();
ctx.moveTo(r.x0,r.y0);
ctx.lineTo(r.x1,r.y1);
ctx.lineTo(r.x2,r.y2);
ctx.lineTo(r.x3,r.y3);
ctx.closePath();
ctx.fillStyle=randomColor();
ctx.fill();
ctx.stroke();
}
// draw the bezier curve points
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=0;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.lineWidth=3;
ctx.strokeStyle='red';
ctx.stroke();
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
body{ background-color:white; padding:10px; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>"Rectangular-ish" polygons along the red cubic bezier curve.</h4>
<canvas id="canvas" width=400 height=300></canvas>
Uniform width polygons:
Again, because they are rectangular-ish polygons, you will not be able to get precisely uniform widths. To get semi-uniform widths:
Calculate many points along the curve (maybe 1000+).
Use the distance formula to reduce the 1000+ point-set to a point-set with uniform distances along the curve: var distance=Math.sqrt( (pt1.x-pt0.x)*(pt1.x-pt0.x) + (pt1.y-pt0.y)*(pt1.y-pt0.y) )
Related
I want to fill this ellipse with count random points inside it any help I'd be glad.
What algorithm of actions?
To generate uniformly distributed points inside ellipse, use approach developed for disk and scale coordinates with needed ratio:
generate two random values
r = A * sqrt(random(0..1))
fi = 2 * Pi * random(0..1)
and point in ellipse with horizontal semiaxis A and vertical one B
x = center.x + r * cos(fi)
y = center.y + B / A * r * sin(fi)
If ellipse is rotated, also rotate these coordinates
I've been trying to draw an arc on the canvas, using p5.js. I got start & end points, the chord length i calculate using pythagoras using the two points, the height & width values are also given.
In order to draw an arc, i need to use the following function;
arc(x, y, w, h, start, stop, [mode], [detail]) for docs refer to here
The start & stop parameters refer to the start&stop angles specified in radians. I can't draw the arc without those angles and i'm unable to calculate them using what i got.
I searched for lots of examples similar to my question, but it is suggested to calculate the center angle, which i'm also unable to do so. Even though i was able to calculate the center angle, how i'm supposed to get the start&stop angles afterwards?
I have drawn some example illustrations on GeoGebra;
The angle of a vector can be calculated by atan2().
Note, that:
tan(alpha) = sin(alpha) / cos(alpha)
If you've a vector (x, y), then than angle (alpha) of the vector relative to the x-axis is:
alpha = atan2(y, x);
The start_angle and stop_angle of an arc, where the center of the arc is (cpt_x, cpt_y), the start point is (spt_x, spt_y) and the end point is (ept_x, ept_y), can be calculated by:
start_angle = atan2(spt_y-cpt_y, spt_x-cpt_x);
stop_angle = atan2(ept_y-cpt_y, ept_x-cpt_x);
See the example, where the stop angle depends on the mouse position:
var sketch = function( p ) {
p.setup = function() {
let sketchCanvas = p.createCanvas(p.windowWidth, p.windowHeight);
sketchCanvas.parent('p5js_canvas')
}
p.windowResized = function() {
p.resizeCanvas(p.windowWidth, p.windowHeight);
}
p.draw = function() {
let cpt = new p5.Vector(p.width/2, p.height/2);
let rad = p.min(p.width/2, p.height/2) * 0.9;
let stop_angle = p.atan2(p.mouseY-cpt.y, p.mouseX-cpt.x);
p.background(192);
p.stroke(255, 64, 64);
p.strokeWeight(3);
p.noFill();
p.arc(cpt.x, cpt.y, rad*2, rad*2, 0, stop_angle);
}
};
var circle = new p5(sketch);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
<div id="p5js_canvas"></div>
I am trying to draw multiple parallel paths based on one set of coordinates like on this example:
I have my path created based on set of segments, then I clone it five times, and translate this way:
var myPath;
var lineData = []; // Long array of segments
myPath.segments = lineData;
for (var i = 1; i < 5; i++) {
var clone = myPath.clone();
clone.translate(new paper.Point(0, i*5));
}
And here is the result I get:
I want the lines to be full parallel but the distance is always different and they sometimes overlap. Is there a way to fix it or mayby i should try different approach in order to create this kind of curved lines?
A cubic bezier curve cannot be expanded to another parallel cubic bezier curve.
#Blindman67's method of calculating normals (perpendiculars to the tangent line) along the original curve & drawing dots on those perpendiculars will work.
To get started, see this SO Q&A that shows the algorithm to calculate points perpendicular to a Bezier curve.
Here's an example proof-of-concept:
If you just want solid parallel curves, then oversample by setting tCount higher (eg tCount=500). This proof-of-concept uses dots to create the line, but for solid curves you can use a set of line points.
To-Do if you want dotted lines: You'll need to oversample with the algorithm (maybe use tCount=500 instead of 60) and de-dupe the resulting point-set until you have points at uniform distance along the curve. Then your dots won't be sparse & clustered along the curve.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// variables defining a cubic bezier curve
var PI2=Math.PI*2;
var s={x:20,y:30};
var c1={x:200,y:40};
var c2={x:40,y:200};
var e={x:270,y:220};
// an array of points plotted along the bezier curve
var points=[];
// we use PI often so put it in a variable
var PI=Math.PI;
// plot 60 points along the curve
// and also calculate the angle of the curve at that point
var tCount=60;
for(var t=0;t<=tCount;t++){
var T=t/tCount;
// plot a point on the curve
var pos=getCubicBezierXYatT(s,c1,c2,e,T);
// calculate the perpendicular angle of the curve at that point
var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
var a = Math.atan2(ty, tx)-PI/2;
// save the x/y position of the point and the perpendicular angle
// in the points array
points.push({
x:pos.x,
y:pos.y,
angle:a
});
}
var PI2=Math.PI*2;
var radii=[-12,-6,0,6,12];
// fill the background
ctx.fillStyle='navy';
ctx.fillRect(0,0,cw,ch);
// draw a dots perpendicular to each point on the curve
ctx.beginPath();
for(var i=0;i<points.length;i++){
for(var j=-2;j<3;j++){
var r=radii[j+2];
var x=points[i].x+r*Math.cos(points[i].angle);
var y=points[i].y+r*Math.sin(points[i].angle);
ctx.moveTo(x,y);
ctx.arc(x,y,1.5,0,PI2);
}
}
ctx.fillStyle='skyblue';
ctx.fill();
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the perpendicular angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Dotted parallel Bezier Curve.</h4>
<canvas id="canvas" width=300 height=300></canvas>
I have created an ellipse on my canvas and now I need to draw three lines stemming from the origin. As an example let's say the first line is 90 degrees (vertical) so the point is (0, 10). I need the other two lines to be x pixels away from the point in both directions.
I'm sure I didn't describe this well enough but basically what I am trying to do is from a point on a known ellipse, find another point x distance away that lies on the ellipse.
I have tried looking for an arc of an ellipse but nothing seems to fit what I am looking for.
For an ellipse:
x = a cos(t)
y = b sin(t)
So:
x/a= cos(t)
t = acos(x/a)
y = b sin(acos(x/a))
Plug in your values of a, b, and x and you get y.
See https://www.mathopenref.com/coordparamellipse.html
Rather crudely:
var a=120;
var b=70;
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var xCentre=c.width / 2;
var yCentre=c.height / 2;
// draw axes
cxt.strokeStyle='blue';
cxt.beginPath();
cxt.moveTo(0, yCentre);
cxt.lineTo(xCentre*2, yCentre);
cxt.stroke();
cxt.beginPath();
cxt.moveTo(xCentre, 0);
cxt.lineTo(xCentre, yCentre*2);
cxt.stroke();
// draw ellipse
cxt.strokeStyle='black';
cxt.beginPath();
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = xCentre - (a * Math.cos(i));
yPos = yCentre + (b * Math.sin(i));
if (i == 0) {
cxt.moveTo(xPos, yPos);
} else {
cxt.lineTo(xPos, yPos);
}
}
cxt.lineWidth = 2;
cxt.strokeStyle = "#232323";
cxt.stroke();
cxt.closePath();
// draw lines with x=+/- 40
var deltaX=40;
var y1=b*Math.sin(Math.acos(deltaX/a));
cxt.strokeStyle='red';
cxt.beginPath();
cxt.moveTo(xCentre+deltaX, yCentre-y1);
cxt.lineTo(xCentre, yCentre);
cxt.lineTo(xCentre-deltaX, yCentre-y1);
cxt.stroke();
<html>
<head><title>Ellipse</title></head>
<body>
<canvas id="myCanvas" style="position: absolute;" width="400" height="200"></canvas>
</body>
</html>
(Using https://www.scienceprimer.com/draw-oval-html5-canvas as a basis as I've never used HTML canvas before.)
Andrew Morton's answer is adequate, but you can it with one square root instead of a sin and an acos.
Suppose you have an ellipse centered at the origin, with a radius along the X-axis of a and a radius along the Y-axis of b. The equation of this ellipse is
x2/a2 + y2/b2 = 1.
Solving this for y gives
y = ± b sqrt(1 - x2/a2)
You can choose whichever sign is appropriate. Based on your post, you want the positive square root.
Translating to Javascript:
function yForEllipse(a, b, x) {
return b * Math.sqrt(1 - x*x / a * a);
}
I'm trying to develop a small application using html5 and canvas/KineticJS. I'd like to trace a number of rays that start from a 2d point to infinite, just setting a custom angle degree. For example, if I set 90° the app should render four rays (two straight lines, one vertical and one horizontal that meet in my 2d point). If I set 60° I should see 3 straight lines, like an asterisk *
The longest line you'll ever have to draw is the size of the canvas's diagonal:
var r = Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2));
Use sin and cos to calculate each of your end points at that radius:
var theta = delta * Math.PI / 180.0;
var dx = r * Math.cos(n * theta);
var dy = r * Math.sin(n * theta);
Then, just draw lines from (x, y) to (x + dx, y + dy). Simples.