Equivalent of canvas quadraticCurveTo in SVG - javascript

I am working on a plugin to allow "natural looking" signatures to be drawn using mouse or touch. When confirmed by the user, the result will be a stored SVG that can then be displayed in place of the "Click to sign" button.
The attached JSFiddle http://jsfiddle.net/TrueBlueAussie/67haj4nt/3/ shows a testbed for what I am trying to do. The SVG generated image should look close to the original canvas paths.
The first div contains a canvas, in which I draw some multiple-segment lines (e.g. paths). Using quadraticCurveTo, and a midpoint for the control point, I draw the lines with smooth curves. This works just fine.
The key part of the curved line drawing is:
$.each(lines, function () {
if (this.length > 0) {
var lastPoint = this[0];
ctx.moveTo(lastPoint[0], lastPoint[1]);
for (var i = 1; i < this.length; i++) {
var point = this[i];
var midPoint = [(lastPoint[0] + point[0]) / 2, (lastPoint[1] + point[1]) / 2];
ctx.quadraticCurveTo(lastPoint[0], lastPoint[1], midPoint[0], midPoint[1]);
lastPoint = point;
}
// Draw the last line straight
ctx.lineTo(lastPoint[0], lastPoint[1]);
}
});
I have tried multiple options for SVG generation of the same output, but I am stumped on how to convert the same sets of points to equivalent curved lines. Quadratic Beziers require "proper" control points, but I would prefer to use the far simpler mid-points if possible.
Any ideas? Is this possible or will I have to convert both to use Beziers with calculated control point(s). Is there a simple way to calculate control points that will do the same job?
jQuery or raw JavaScript solutions are fine, but you need to demonstrate in the JSFiddle provided :)

It's just a bug in your code. You are not updating lastPoint in your SVG version.
http://jsfiddle.net/67haj4nt/4/
And if you update the SVG version to match the canvas version, you get identical curves.
http://jsfiddle.net/67haj4nt/5/

Related

Canvas: curve failing to start from end point of previous curve

I'm using the algorithm posted by the author of the question in the below thread to draw an N point bezier curve defined by some array of points.
how to draw smooth curve through N points using javascript HTML5 canvas?
Here's a fiddle of the project:
http://jsfiddle.net/lee2808/2YVx4/
If you copy and paste that into a js file and replace "AddImage.png" in the Curve's Ctor call on line 15 with an image file, all should work fine! You need to click on the canvas three times for the curve to begin to draw.
I'm dynamically adding points on a mousedown + mouseup event.
Anyway my implementation half works (if that's a thing lol). Once I have placed the first 3 points on the canvas a bezier is drawn as expected. However when I add further points the start point for the next curve is not at the end point of the previous curve.
It seems it's starting from the previous point.
Anyway here's my implementation :
Curve.prototype.drawCurve = function(pContext){
pContext.save();
if(this.getPoints().length >2){
pContext.moveTo(this.getPoints()[0].getX(),this.getPoints()[0].getY());
var i = 1;
for(i; i < this.getPoints().length-2; i++){
var modX = (this.getPoints()[i].getX() + this.getPoints()[i+1].getX()) /2;
var modY = (this.getPoints()[i].getY() + this.getPoints()[i+1].getY()) /2;
pContext.quadraticCurveTo(this.getPoints()[i].getX(), this.getPoints()[i].getY(),modX,modY);
}
if(this.getPoints().length > 2){
pContext.quadraticCurveTo(this.getPoints()[i].getX(),this.getPoints()[i].getY(), //last control point
this.getPoints()[i+1].getX(),this.getPoints()[i+1].getY());//end point
}
pContext.stroke();
}
pContext.restore();
};
Pretty much identical. Can anyone see the flaw in my logic?
I'm trying to produce a chain of bezier curves so that I can then animate an object to follow that path incase anyones interested as to why I want to do this.
Thanks in advance!

Is there any way to get these 'canvas' lines drawn in one loop?

I am simulating a page turn effect in html5 canvas.
On each page I am drawing lines to simulate lined paper.
These lines are drawn as the page is turned and in order to give natural perspective I am drawing them using quadratic curves based of several factors (page turn progress, closeness to the center of the page etc.. etc...)
The effect is very natural and looks great but I am looking for ways to optimize this.
Currently I am drawing every line twice, once for the actual line and once for a tiny highlight 1px below this line. I am doing this like so:
// render lines (shadows)
self.context.lineWidth = 0.35;
var midpage = (self.PAGE_HEIGHT)/2;
self.context.strokeStyle = 'rgba(0,0,0,1)';
self.context.beginPath();
for(i=3; i < 21; i++){
var lineX = (self.PAGE_HEIGHT/22)*i;
var curveX = (midpage - lineX) / (self.PAGE_HEIGHT);
self.context.moveTo(foldX, lineX);
self.context.quadraticCurveTo(foldX, lineX + ((-verticalOutdent*4) * curveX), foldX - foldWidth - Math.abs(offset.x), lineX + ((-verticalOutdent*2) * curveX));
}
self.context.stroke();
// render lines (highlights)
self.context.strokeStyle = 'rgba(255,255,255,0.5)';
self.context.beginPath();
for(i=3; i < 21; i++){
var lineX = (self.PAGE_HEIGHT/22)*i;
var curveX = (midpage - lineX) / (self.PAGE_HEIGHT);
self.context.moveTo(foldX, lineX+2);
self.context.quadraticCurveTo(foldX, lineX + ((-verticalOutdent*4) * curveX) + 1, foldX - foldWidth - Math.abs(offset.x), lineX + ((-verticalOutdent*2) * curveX) + 1);
}
self.context.stroke();
As you can see I am opening a path, looping through each line, then drawing the path. Then I repeat the whole process for the 'highlight' lines.
Is there any way to combine both of these operations into a single loop without drawing each line individually within the loop which would actually be far more expensive?
This is a micro-optimization, I am well aware of this. However this project is a personal exercise for me in order to learn html5 canvas performance best practices/optimizations.
Thanks in advance for any suggestions/comments
Paths can be stroked as many times as you like, they're not cleared when you call .stroke(), so:
create your path (as above)
.stroke() it
translate the context
change the colours
.stroke() it again
EDIT tried this myself - it didn't work - the second copy of the path didn't notice the translation of the coordinate space :(
It apparently would work if the path was created using new Path() as documented in the (draft) specification instead of the "current default path" but that doesn't appear to be supported in Chrome yet.

Javascript physics engine and simulated infinite curve

I'm trying to do a Tiny Wings like in javascript.
I first saw a technique using Box2D, I'm using the closure-web version (because of the memory leaks fix).
In short, I explode the curve into polygons so it looks like that:
I also tried with Chipmunk-js and I use the segment shape to simulate my ground like that:
In both cases, I'm experiencing some "crashes" or "bumps" at the common points between polygons or segments when a circle is rolling.
I asked about it for Chipmunk and the author said he implemented a radius property for the segment to reduce this behavior. I tried and it indeed did the trick but it's not perfect. I still have some bumps(I had to set to 30px of radius to get a positive effect).
The "bumps" append at the shared points between two polygons :
Using, as illandril suggested to me, the edging technique (he only tested with polygon-polygon contact) to avoid the circle to crash on an edge:
Also tried to add the bullet option as Luc suggested and nothing seems to change.
Here the demo of the issue.
You can try to change the value to check :
bullet option
edge size
iterations count
the physics
(only tested on latest dev Chrome)
Be patient (or change the horizontal gravity) and you'll see what I mean.
Here the repo for the interested.
The best solution is edge shapes with ghost vertices, but if that's not available in the version/port you're using, the next best thing is like the diagram in your question called 'edging', but extend the polygons further underground with a very shallow slope, like in this thread: http://www.box2d.org/forum/viewtopic.php?f=8&t=7917
I first thought the problem could come from the change of slope between two adjacent segments, but since on a flat surface of polygons you still have bumps I think the problem is rather hitting the corner of a polygon.
I don't know if you can set two sets of polygons, overlapping each other ? Just use the same interpolation calculations and generate a second set of polygons just like in the diagram hereafter : you have the red set of polygons built and add the green set by setting the left vertices of a green polygon in the middle of a red polygon, and its right vertices in the middle of the next red polygon.
![diagram][1]
This should work on concave curves and... well you should be flying over the convex ones anyway.
If this doesn't work try setting a big number of polygons to build the slope. Use a tenth of the circle's radius for the polygon's width, maybe even less. That should reduce your slope discontinuities.
-- Edit
In Box2D.js line 5082 (in this repo at least) you have the PreSolve(contact, manifold) function that you can override to check if the manifolds (directions in which the snowball are impulsed when colliding the polygons) are correct.
To do so, you would need to recover the manifold vector and compare it to the normal of the curve. It should look like that (maybe not exactly) :
Box2D.Dynamics.b2ContactListener.prototype.PreSolve = function (contact, oldManifold) {
// contact instanceof Box2D.Dynamics.Contacts.b2Contact == true
var localManifold, worldManifold, xA, xB, man_vect, curve_vect, normal_vect, angle;
localManifold = contact.GetManifold();
if(localManifold.m_pointCount == 0)
return; // or raise an exception
worldManifold = new Box2D.Collision.b2WorldManifold();
contact.GetWorldManifold( worldManifold );
// deduce the impulse direction from the manifold points
man_vect = worldManifold.m_normal.Copy();
// we need two points close to & surrounding the collision to compute the normal vector
// not sure this is the right order of magnitude
xA = worldManifold.m_points[0].x - 0.1;
xB = worldManifold.m_points[0].x + 0.1;
man_vect.Normalize();
// now we have the abscissas let's get the ordinate of these points on the curve
// the subtraction of these two points will give us a vector parallel to the curve
var SmoothConfig;
SmoothConfig = {
params: {
method: 'cubic',
clip: 'mirror',
cubicTension: 0,
deepValidation: false
},
options: {
averageLineLength: .5
}
}
// get the points, smooth and smooth config stuff here
smooth = Smooth(global_points,SmoothConfig);
curve_vect = new Box2D.Common.Math.b2Vec2(xB, smooth(xB)[1]);
curve_vect.Subtract(new Box2D.Common.Math.b2Vec2(xA, smooth(xA)[1]));
// now turn it to have a normal vector, turned upwards
normal_vect = new Box2D.Common.Math.b2Vec2(-curve_vect.y, curve_vect.x);
if(normal_vect.y > 0)
normal_vect.NegativeSelf();
normal_vect.Normalize();
worldManifold.m_normal = normal_vect.Copy();
// and finally compute the angle between the two vectors
angle = Box2D.Common.Math.b2Math.Dot(man_vect, normal_vect);
$('#angle').text("" + Math.round(Math.acos(angle)*36000/Math.PI)/100 + "°");
// here try to raise an exception if the angle is too big (maybe after a few ms)
// with different thresholds on the angle value to see if the bumps correspond
// to a manifold that's not normal enough to your curve
};
I'd say the problem has been tackled in Box2D 2.2.0 , see its manual, section 4.5 "Edge Shapes"
The thing is it's a feature of the 2.2.0 version, along with the chainshape thing, and the box2dweb is actually ported from 2.2.1a - don't know about box2dweb-closure.
Anything I've tried by modifying Box2D.Collision.b2Collision.CollidePolygonAndCircle has resulted in erratic behaviour. At least a part of the time (e.g. ball bumping in random directions, but only when it rolls slowly).

How could I simplify a complex polygon?

Recently I've been thinking about how to transform a complex polygon into a non-complex polygon. How is this done?
This is the sort of thing I want to do:
I'm going to end up with JavaScript when I'm done, but any form of a solution is fine (language, algorithm, or just plain English).
I would use the same heuristic that I would use when drawing the polygon by hand (which is probably not the most mathematically efficient way to caluclaute that polygon, but probably the easiest to understand/implement).
Start at a point
Find all the intersections between my current point and the point I'm trying to get to
If none exist draw to the next point
If one does, then draw to there, and then set the next point to the next point from there
If you aren't back to the beginning then goto 2.
Here is an example implementation on jsfiddle. Note: it isn't optimized.
I believe the easiest route is to perform a plane sweep to detect all the edge-edge intersections. It is not difficult to augment a basic plane-sweep algorithm implementation to maintain
the outermost boundary, which is what you want. Almost every textbook on computational geometry explains this well.
This is a late answer, but this can be done using Javascript Clipper Library. The desired operation is Simplifying (which internally uses Union operation) and it removes self-intersections where edge(s) crosses other edge(s).
Note! Javascript Clipper 5 cannot ensure that in all cases the solution consists only of truly simple polygons. This like special case is the vertices touching edges. Clipper 6 (Javascript version not yet ready) can handle these like special cases also.
Simplifying Polygons using Javascript Clipper Main Demo
You can play with Clipper using Javascript Clipper Main Demo. Click Polygons-Custom and you can input your own polygon there and then make the desired operations.
Let's take your example:
[[7,86, 196,24, 199,177, 47,169, 51,21, 224,102, 223,146, 7,140, 7,86]]
If you input these points in demo (as Subject or Clip), you get the following polygon:
Then make a Simplify operation which produces the following solution:
If you click Solution in Polygon Explorer, you can see the coordinates of simplified polygon:
[[199,177, 47,169, 47.75,141.13, 7,140, 7,86, 49.62,72.02, 51,21, 114.51,50.73, 196,24, 197.28,89.49, 224,102, 223,146, 198.38,145.32]]
Code example of Simplifying Polygons
And finally, I put the full functional code in JSBIN, which includes SVG drawing functions and is therefore rather long:
<html>
<head>
<title>Javascript Clipper Library / Simplifying Polygons</title>
<script src="clipper.js"></script>
<script>
function draw() {
var subj_polygons = [[{"X":7,"Y":86},{"X":196,"Y":24},{"X":199,"Y":177},{"X":47,"Y":169},{"X":51,"Y":21},{"X":224,"Y":102},{"X":223,"Y":146},{"X":7,"Y":140},{"X":7,"Y":86}]];
var scale = 100;
subj_polygons = scaleup(subj_polygons, scale);
var cpr = new ClipperLib.Clipper();
cpr.AddPolygons(subj_polygons, ClipperLib.PolyType.ptSubject);
var solution_polygons = new ClipperLib.Polygons();
solution_polygons = cpr.SimplifyPolygons(subj_polygons, ClipperLib.PolyFillType.pftNonZero);
//console.log(JSON.stringify(solution_polygons));
var svg = '<svg style="margin-top:10px; margin-right:10px;margin-bottom:10px;background-color:#dddddd" width="240" height="200">';
svg += '<path stroke="black" fill="yellow" stroke-width="2" d="' + polys2path(solution_polygons, scale) + '"/>';
svg += '</svg>';
document.getElementById('svgcontainer').innerHTML = svg;
}
// helper function to scale up polygon coordinates
function scaleup(poly, scale) {
var i, j;
if (!scale) scale = 1;
for(i = 0; i < poly.length; i++) {
for(j = 0; j < poly[i].length; j++) {
poly[i][j].X *= scale;
poly[i][j].Y *= scale;
}
}
return poly;
}
// converts polygons to SVG path string
function polys2path (poly, scale) {
var path = "", i, j;
if (!scale) scale = 1;
for(i = 0; i < poly.length; i++) {
for(j = 0; j < poly[i].length; j++) {
if (!j) path += "M";
else path += "L";
path += (poly[i][j].X / scale) + ", " + (poly[i][j].Y / scale);
}
path += "Z";
}
return path;
}
</script>
</head>
<body onload="draw()">
<h2>Javascript Clipper Library / Simplifying Polygons</h2>
This page shows an example of simplifying polygon and drawing it using SVG.
<div id="svgcontainer"></div>
</body>
</html>
You should maintain list of incident edges for every intersection point.
Then for ever point choose edge (outgoing), which has the smallest angle (anti-clockwise) with the previous (incoming) edge.
It seems like you're going to want to look into Convex Hull algorithms. Here's an applet of a few Convex Hull algorithms. You might be able to modify one of the algorithms to get your extreme points and go from there.
Ok, it seems I made working solution:
http://mrpyo.github.com/Polygon/
It's ActionScript so you should have no problem translating it into JavaScript.
I can explain used algorithm if somebody is interested...

How can I stop elements overlapping using JavaScript and the Raphael JavaScript library

I’m generating multiple, random sized, circular elements using the Raphael JavaScript library but because it’s random a lot of the circular elements being generate overlap or cover each other. What I wanted to know, is there any way with JavaScript to tell if one element is in already in particular position so to avoid the overlapping? Essentially, I want to create random elements on a canvas, of a random size that don’t overlap or cover each other.
There's a couple of test files I created here to give you an idea of what I'm doing. The first one generates random objects and the second link sets them to a grid to stop the overlapping.
http://files.nicklowman.co.uk/movies/raphael_test_01/
http://files.nicklowman.co.uk/movies/raphael_test_03/
The easiest way is to create an object and give it a repulsive force that degrades towards zero at it's edge. As you drop these objects onto the canvas the objects will push away from each other until they reach a point of equilibrium.
Your examples aren't working for me, so I cannot visualize your exact scenario.
Before you "drop" an element on the canvas, you could query the positions of your other elements and do some calculations to check if the new element will overlap.
A very simple example of this concept using circle elements might look like this:
function overlap(circ1, circ2) {
var attrs = ["cx", "cy", "r"];
var c1 = circ1.attr(attrs);
var c2 = circ2.attr(attrs);
var dist = Math.sqrt(Math.pow(c1.cx - c2.cx ,2) + Math.pow(c1.cy - c2.cy, 2));
return (dist < (c1.r + c2.r));
}
var next_drop = paper.circle(x, y, r);
for (var i in circles) {
if (overlap(next_drop, circles[i])) {
// do something
}
}
Of course calculating just where you're going to place a circle after you've determined it overlaps with others is a little more complicated.

Categories

Resources