For one of my application I would need to draw a dashed curves on the bezier path in Html5 canvas... The dash' length and gaps in between should be variable... It is achivable in JavaFx, see this link... I would like to achieve same effect using Html5 canvas. I know how to draw dashed straight lines, but not curved lines along the bezier...
Though I am not an expert, I know the bezier drawing algorithm, problem I see with this algorithm is, it allows you to identify coordinates on the bezier using the time parameter which ranges from 0 to 1...
This is not sufficient because to draw a dashed bezier, I would need to draw many small beziers, with specified length parameter and at given gap distance, on the main bezier path. There must be some algorithm which is used by JavaFx. If anyone can help me out that would be great.
I would presume that JavaFX is using a general technique for drawing any dashed curve and just happens to be using it on a bezier in that example.
The hard part is figuring out where to start and stop each dash, which requires knowing the arc length of your bezier curve at various points along it.
There is an analytic approach, but I would suggest the following:
var bezier = function(controlPoints, t) {
/* your code here, I'll presume it returns a 2-element array of x and y. */
};
//just figure out the coordinates of all the points in each dash, don't draw.
//returns an array of arrays, each sub-array will have an even number of nu-
//merical elements, to wit, x and y pairs.
//Argument dashPattern should be an array of alternating dash and space
//lengths, e.g., [10, 10] would be dots, [30, 10] would be dashes,
//[30, 10, 10, 10] would be 30-length dash, 10-length spaces, 10-length dash
// and 10-length space.
var calculateDashedBezier = function(controlPoints, dashPattern) {
var step = 0.001; //this really should be set by an intelligent method,
//rather than using a constant, but it serves as an
//example.
//possibly gratuitous helper functions
var delta = function(p0, p1) {
return [p1[0] - p0[0], p1[1] - p0[1]];
};
var arcLength = function(p0, p1) {
var d = delta(p0, p1);
return Math.sqrt(d[0]*d[0] + d[1] * d[1]);
};
var subPaths = [];
var loc = bezier(controlPoints, 0);
var lastLoc = loc;
var dashIndex = 0;
var length = 0;
var thisPath = [];
for(var t = step; t <= 1; t += step) {
loc = bezier(controlPoints, t);
length += arcLength(lastLoc, loc);
lastLoc = loc;
//detect when we come to the end of a dash or space
if(length >= dashPattern[dashIndex]) {
//if we are on a dash, we need to record the path.
if(dashIndex % 2 == 0)
subPaths.push(thisPath);
//go to the next dash or space in the pattern
dashIndex = (dashIndex + 1) % dashPattern.length;
//clear the arclength and path.
thisPath = [];
length = 0;
}
//if we are on a dash and not a space, add a point to the path.
if(dashIndex % 2 == 0) {
thisPath.push(loc[0], loc[1]);
}
}
if(thisPath.length > 0)
subPaths.push(thisPath);
return subPaths;
};
//take output of the previous function and build an appropriate path
var pathParts = function(ctx, pathParts) {
for(var i = 0; i < pathParts.length; i++) {
var part = pathParts[i];
if(part.length > 0)
ctx.moveTo(part[0], part[1]);
for(var j = 1; j < part.length / 2; j++) {
ctx.lineTo(part[2*j], part[2*j+1]);
}
}
};
//combine the above two functions to actually draw a dashed curve.
var drawDashedBezier = function(ctx, controlPoints, dashPattern) {
var dashes = calculateDashedBezier(controlPoints, dashPattern);
ctx.beginPath();
ctx.strokeStyle = /* ... */
ctx.lineWidth = /* ... */
pathParts(ctx, dashes);
ctx.stroke();
};
The main problem with this approach is its unintelligent granularity. When step is too big for your (small) dashes or (big) curve, the step size will not work well and dash boundaries will not fall exactly where you want them to. When step is too small, you may end up doing lineTo()s on points that are a sub-pixel distance away from each other, making for AA artifacts sometimes. Filtering out sub-pixel distance coordinates is not hard, but it is inefficient to generate more 'vertices' than you really need. Coming up with a better step size is actually something I'd consider attacking more analytically.
There is one bonus to using this approach: if you replace bezier(controlPoints, t) with anything else that evaluates to a curve, you'll be drawing dashed whatevers!-- again with the same potential problems listed in the previous paragraph. But a really good solution to the granularity problem could work for all 'well-behaved' curves.
In the future we might be able to use context.setLineDash(segments) :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#line-styles
Related
I want to develop a game "Cut the Shape" in JavaScript. Several components with convex polygons that will need to be clipped depending on the number of convex freedom faces. Lines and polygons are displayed on the Canvas. The Paper.js graphics load to use.
But then the question arose, which concerns the correct separation of the polygons from each other relative to the drawn line, I just can’t think of a way to do this.
Here is an example on simple polygons (the lines can be absolutely any, the user draws them himself):
I got to the points of dividing the polygon into other polygons using small dots with a line:
var polygon = new Path.RegularPolygon(new Point(200, 300), 4, 100);
polygon.strokeColor = 'blue';
polygon.fullySelected = true;
var shapesArray = [];
shapesArray.push(polygon);
function splitShape(path1, path2){
var shapesArrayCopy = path1.slice(0);
shapesArray = [];
for(var i = 0; i < shapesArrayCopy.length; i++){
var intersections = shapesArrayCopy[i].getIntersections(path2);
if(intersections.length >= 2){
var p1 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[0].point));
var p2 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[1].point));
p1.closed = true;
p2.closed = true;
shapesArray.push(Object.assign(p1));
shapesArray.push(Object.assign(p2));
path2.visible = false;
}
else{
shapesArray.push(shapesArrayCopy[i])
}
}
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
myPath.add(event.point);
myPath.add(event.point);
}
function onMouseDrag(event) {
myPath.segments.pop();
myPath.add(event.point);
}
function onMouseUp(event) {
splitShape(shapesArray, myPath)
myPath.visible = false;
}
In 2D You just displace the cuts in perpendicular direction to the cut line. If your cut line endpoints are: p0(x0,y0),p1(x1,y1) then the line direction is:
dp = p1-p0 = (x1-x0,y1-y0)
make it unit:
dp /= sqrt((dp.x*dp.x)+(dp.y*dp.y)
make it equal to half of the gap between cuts:
dp *= 0.5*gap
now tere are two perpendicular directions:
d0 = (-dp.y,+dp.x)
d1 = (+dp.y,-dp.x)
so now just add d0 to all vertexes of one cut, and d1 to the other one. Which use for which is simply you take point that does not lie on the cutting line (for example avg point of your cut) p and compute (only once for polygon cut):
t = dot(p-p0,d0) = ((p.x-x0)*d0.x)+((p.y-y0)*d0.y)
if (t>0) use d0, if (t<0) use d1 and if (t==0) you chose wrong point p as it lies on cutting line.
I struggle with both anything math, and describing my problems, so I'm going to keep include lots of images to help visualize it. I've been plotting prime numbers on a polar graph, to demonstrate the patterns that emerge.
For anyone unfamiliar, it looks like this.
Visualization
The problem that I've been having is that eventually the circles get too small to be seen. The code I'm using looks like this:
let radius;
let theta;
let toCalc = 50000
let primes;
let size = 1;
function setup() {
createCanvas(800,800);
primes = getPrimes(toCalc);
//put origin point in center
r = height * 0.45;
theta = 0;
noStroke();
color(255,255,255)
scale(0.01)
}
function draw() {
translate(width / 2, height / 2);
background(10);
scale(size);
//the for loop takes forever to fully create all of the circles, so it takes a really long time //to fully generate so i might leave it overnight sometime
for(var i = 0; i < toCalc; i++)
{
current = primes[i];
let x = current * cos(current);
let y = current * sin(current);
//the horrific (5/exp(size)*2) is something i threw together to get exponential scaling
//with the zoom at roughly the right level
circle(x, y, (5/exp(size))*3)
}
console.log(size)
}
function getPrimes(max) {
//i timed this code and apparently its actually really really performant,
//even up to a million primes. its the for loop that really kills performance.
var sieve = [], i, j, primes = [];
for (i = 2; i <= max; ++i) {
if (!sieve[i]) {
// i has not been marked -- it is prime
primes.push(i);
for (j = i << 1; j <= max; j += i) {
sieve[j] = true;
}
}
}
return primes;
}
//measuring mousewheel for zoom
function mouseWheel(event) {
//ternary to check if it was a scrolldown or scrollup
//have to make zoom out by size/10 because size is represented by a value from 0 to 1
event.delta >= 0 ? size-=size/10 : size+=size/10
//returning false to block page scrolling
return false;
}
Currently, totally arbitrarily, I have been using this to scale the circles, and it works relatively well up to a scale of 0.005 on a scale of 0 to 1, but then the circles get too small and disappear.
5/exp(size))*3
What would be the best way to scale circles like this as the user zoomed out?
I have to make 3d text from font glyphs. Yes, I know that I can use TextGeometry, but I need to draw this manually because I need to do offset on font splines.
At this moment I have splines with their points and I can draw letters.
From points I know: previousPoint, currentPoint and nextPoint and I need to compute bisector between previous and next points and I have no idea to do that.
Or if is another way to move spline points outer of initial position to do offset.
My idea:
Thank you!
EDIT:
With yours answers I obtained correct values for each splines from font, but only at 'o' and '0' I have a problem.
This method draw a weird tangent in bottom of the letter and I don't know to resolve this problem..
here is the result
Anybody know how to resolve this?
EDIT 2:
Finally I finished my project. And this is the final product ( .stl exporter )
final offset
Thank you for yours answers!
There are the result from: x = (prev_x + next_x) / 2 and y = (prev_y + next_y) / 2
wrong result
desired result
Here is my code where let points is all the points from the path:
getPathPoints(path) {
let points = path.getPoints();
console.log(points)
for (let i = 0; i < points.length; i++) {
let A = points.extended(i - 1); // previousPoint => where extends is a custom array prototype
let B = points.extended(i); // currentPoint
let C = points.extended(i + 1); // nextPoint
let x = (A.x + C.x) / 2;
let y = (A.y + C.y) / 2;
let bisector = new THREE.Vector2(x,y);
console.log(bisector);
}
}
What splines describe your glyphs?
I know that TTF fonts use quadratic Bezier curves. For Bezier direction vector in starting and ending points has direction onto control point. So difference
S = ControlPoint[1] - ControlPoint[0]
represents direction in the starting point, difference
E = ControlPoint[1] - ControlPoint[2]
represents direction in the ending point.
Normalize these vectors for two neighbour curves and add them - now you have bisector vector.
Bisector = E(i).Normalized + S(i+1).Normalized
I'm trying to detect polygons on a canvas. I'm using code from this stack overflow question https://stackoverflow.com/a/15308571/3885989
function Vec2(x, y) {
return [x, y]
}
Vec2.nsub = function (v1, v2) {
return Vec2(v1[0] - v2[0], v1[1] - v2[1])
}
// aka the "scalar cross product"
Vec2.perpdot = function (v1, v2) {
return v1[0] * v2[1] - v1[1] * v2[0]
}
// Determine if a point is inside a polygon.
//
// point - A Vec2 (2-element Array).
// polyVerts - Array of Vec2's (2-element Arrays). The vertices that make
// up the polygon, in clockwise order around the polygon.
//
function coordsAreInside(point, polyVerts) {
var i, len, v1, v2, edge, x
// First translate the polygon so that `point` is the origin. Then, for each
// edge, get the angle between two vectors: 1) the edge vector and 2) the
// vector of the first vertex of the edge. If all of the angles are the same
// sign (which is negative since they will be counter-clockwise) then the
// point is inside the polygon; otherwise, the point is outside.
for (i = 0, len = polyVerts.length; i < len; i++) {
v1 = Vec2.nsub(polyVerts[i], point)
v2 = Vec2.nsub(polyVerts[i + 1 > len - 1 ? 0 : i + 1], point)
edge = Vec2.nsub(v1, v2)
// Note that we could also do this by using the normal + dot product
x = Vec2.perpdot(edge, v1)
// If the point lies directly on an edge then count it as in the polygon
if (x < 0) {
return false
}
}
return true
}
It's working OK, but with more complex shapes, it doesn't work that well... Here's a link to the isolated code with an example of one shape that works and one that doesn't:
http://jsfiddle.net/snqF7/
ah! like GameAlchemist said, the issue is that this algorithm is meant for convex polygons,
here's a new && improved algorithm which works with non-convex ( or complex ) polygons ( not perfect ) which I translated from these C code examples ( http://alienryderflex.com/polygon/ )
function pointInPolygon(point, polyVerts) {
var j = polyVerts.length - 1,
oddNodes = false,
polyY = [], polyX = [],
x = point[0],
y = point[1];
for (var s = 0; s < polyVerts.length; s++) {
polyX.push(polyVerts[s][0]);
polyY.push(polyVerts[s][1]);
};
for (var i = 0; i < polyVerts.length; i++) {
if ((polyY[i]< y && polyY[j]>=y
|| polyY[j]< y && polyY[i]>=y)
&& (polyX[i]<=x || polyX[j]<=x)) {
oddNodes^=(polyX[i]+(y-polyY[i])/(polyY[j]-polyY[i])*(polyX[j]-polyX[i])<x);
}
j=i;
}
return oddNodes;
}
and here's a working fiddle: http://jsfiddle.net/snqF7/3/
The algorithm you are using require the polygon to be convex.
Convex mean, in a nutshell, that you can draw a line in between any two points in the polygon, all the line will be inside the polygon.
- More or less a potato or a square :) -.
Solution is either to split your polygons in convex parts or to go for a more complex algorithm that handles non-convex polygons.
To handle non convex polygons, idea is to take a reference point you know is (or isn't) in the polygon, then draw a line between your tested point and this reference point, then count how many times and in which direction it intersect with the polygon segments. Count +1 or -1 on each cross, and the point is inside if final sum is null.
EDIT: I updated the program with the answer and it works great!
I am making a program (feel free to try it out) that lets users draw polygons which it then triangulates. They can click to add vertices and hit enter to triangulate. Anyways, the algorithm works fine as long as I tell it if the points were drawn in a clockwise or counterclockwise fashion (right now I have it set only to work with clockwise polygons). I have been trying to figure this out for days, but have no idea how to determine whether the points are clockwise or counterclockwise. Try drawing shapes with the program mentioned earlier to get a better idea, you can experience what I am talking about better than I can try to explain it.
Here is how the points are defined:
function Point(x, y) {
this.x = x;
this.y = y;
}
var vertices = [];
// Called on click
function addPoint(mouseX, mouseY) {
vertices.push(new Point(mouseX, mouseY));
}
Here is an image of a clockwise polygon:
Here is an image of a counterclockwise polygon:
If you could help me figure out how to determine the "clockwise-ness" of the points, I would be very grateful!
Compute the polygon area using the shoelace formula, but without the absolute value sign. If the result is positive, the points are ordered counterclockwise, and if negative - clockwise.
function polygonArea() {
var area = 0;
for (var i = 0; i < vertices.length; i++) {
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
}
return area / 2;
}
var clockwise = polygonArea() > 0;
In case someone is using three.js the ShapeUtils comes with an inbuilt isClockWise method which internally uses the area method to determine the sign of the calculated area.
isClockWise: function ( pts ) {
return ShapeUtils.area( pts ) < 0;
}
The ShapeUtils.isClockWise Method can be found here.
area: function ( contour ) {
var n = contour.length;
var a = 0.0;
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
}
return a * 0.5;
},
The ShapeUtils.area Method can be found here.
A general idea would be to take a look at the convex hull of your polygone and guess the orientation from there. However, I think that you do not need to build the whole hull to find the orientation, but just one segment belonging to it.
So:
Find two points of your polygones so that all the other points are on one side of this line.
If all the points are on the left (just check one of the points), it's counterclockwise. If they are on the right, it's clockwise.
Example:
On the top figure: 4-5 let the figure on the right, 5-11 let the figure on the right, ...
On the bottom figure: 6-7 let the figure on the left, 7-14 let the figure on the left, ...
Warning: While "walking" on your polygon, do not restart the numeration, otherwise it will be wrong. On the top figure, 4-(n-1) let the figure on the left!
Your intuitive definition of clockwisedness is not well defined. For example, If I draw a horseshoe:
/---a-b--\
/ _d_c_ \
/ / \ \
| | | |
| | | |
\ \ / /
\ \ / /
-- --
If 0 = a < b < b < d and I look at a and b I would conclude from your description that the shape has been drawn clockwise, but if 0 = c < d < a < b I would conclude that the shape has been drawn anticlockwise. Since both of these scenarios involve the same direction in which the points were drawn, just from different starting points, I can only conclude that your definition is lacking.
The horseshoe I drew isn't the best; the idea is that it is almost a circle with just a small hole at the bottom, to allow the other side to be drawn in the opposite direction.
If you are interested in defining things more strictly, then I suggest something along the following lines:
Considering any finite simple polygon as separating the plane into two distinct areas (one finite and one infinite), we can always consider the finite area to be the interior of the polygon. In such a scenario we define a vertex ordering to be clockwise iff the order of the points runs with the exterior along its right-hand side. This is called curve orientation.
Once you have this more solid definition, implementation can be as simple as counting the winding number. Take the midpoint of any ordered pair, say 0 and 1, take a line segment to the right of the ordered pair (at any angle, say perpendicular), and count how many intersections it has with other line segments: The curve is clockwise iff the number is odd.
This is simple to implement, linear in time O(n), and adds constant space O(1).
This a function function that specialized for OpenLayers. As You Can See The Condition Of Clockwise Polygon Is area<0 This Reference Confirm It.
function IsClockwise(feature)
{
if(feature.geometry==null)return -1;
var vertices=feature.geometry.getVertices();
var area=0;
for (var i = 0; i < (vertices.length); i++)
{
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
// console.log(area);
}
return (area < 0);
}