I'm trying to perform Boolean Operations on SVG Paths (that contain beziers, both quadratic and cubic) using JS Clipper.
JS Clipper starts with polygons then performs the operation and then it seems to convert them back to SVG paths.
The function below gives an SVG path but the below example starts with 2 polygons.
An example function:
// Polygon Arrays are expanded for better readability
function clip() {
var subj_polygons = [
[{
X: 10,
Y: 10
}, {
X: 110,
Y: 10
}, {
X: 110,
Y: 110
}, {
X: 10,
Y: 110
}],
[{
X: 20,
Y: 20
}, {
X: 20,
Y: 100
}, {
X: 100,
Y: 100
}, {
X: 100,
Y: 20
}]
];
var clip_polygons = [
[{
X: 50,
Y: 50
}, {
X: 150,
Y: 50
}, {
X: 150,
Y: 150
}, {
X: 50,
Y: 150
}],
[{
X: 60,
Y: 60
}, {
X: 60,
Y: 140
}, {
X: 140,
Y: 140
}, {
X: 140,
Y: 60
}]
];
var scale = 100;
subj_polygons = scaleup(subj_polygons, scale);
clip_polygons = scaleup(clip_polygons, scale);
var cpr = new ClipperLib.Clipper();
cpr.AddPolygons(subj_polygons, ClipperLib.PolyType.ptSubject);
cpr.AddPolygons(clip_polygons, ClipperLib.PolyType.ptClip);
var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
var clip_fillType = ClipperLib.PolyFillType.pftNonZero;
var clipTypes = [ClipperLib.ClipType.ctUnion];
var clipTypesTexts = "Union";
var solution_polygons, svg, cont = document.getElementById('svgcontainer');
var i;
for (i = 0; i < clipTypes.length; i++) {
solution_polygons = new ClipperLib.Polygons();
cpr.Execute(clipTypes[i], solution_polygons, subject_fillType, clip_fillType);
console.log(polys2path(solution_polygons, scale));
}
}
// 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;
}
I assume that you mean some sort of svg path to polygon conversion.
I have searched a lot, but not found anything reliable and out-of-the-box solution.
SVG path can consist of ten different segment, or 20 if we take into account both relative and absolute coordinates. They are represented as letters in path element's d-attribute: relative ones are mhvlcqastz and absolute ones are MHVLCQASTZ. Each have different attributes, a (elliptical arc) being the most complicated one. The most usable and flexible of types is c (cubic bezier curve), because it can represent all other types in rather high precision as these examples show: http://jsbin.com/oqojan/32, http://jsbin.com/oqojan/42.
Raphael JS library has Path2Curve-function which can convert all path segments to cubic curves and it can handle also the complicated arc to cubic conversion. Unfortunately it has a bug, so that it cannot handle all possible path segment combinations, but fortunately there is a fixed version of library available: http://jsbin.com/oqojan/32/edit (look at the Javascript-window).
When all path segments are converted to cubic curves, they can be converted to individual line segments. There are few ways, and the best seems to be an adaptive recursive subdivision method, which produces more line segments in sharp turns of curve and fewer in other parts of curve to achieve a balance of curve fidelity and low count of segments to maximize rendering speed, but unfortunately it could not handle all collinear cases. I succeeded in converting AntiGrain's method to Javascript and added presplitting functionality, which splits the curve in local extremes (first derivative roots) and after that the AntiGrain method handles also all possible collinear cases:
Collinear horizontal: http://jsbin.com/ivomiq/6
Set of different cases: http://jsbin.com/ivomiq/7
Random: http://jsbin.com/ivomiq/8
Collinear rotated: http://jsbin.com/ivomiq/9
All the above samples have two paths in top of each other to show possible errors in adaptive algorithm: the red curve is splitted using very slow brute force method and the green one is splitted using AntiGrain method. If you see not red at all, the AntiGrain's method approximate()-function is working as expected.
OK, now we have repaired Raphael and repaired AntiGrain. If we combine these both methods, we can create a function that converts ANY svg path element to polygon (single or multiple subpolygons). I'm not 100% sure that this is the best or fastest method, but it should be usable. Of course the best would be native browser implementation...
you can use De Casteljau's algorithm to break bezier curve into smaller straight lines, and join them to create polygon.
Here is some references of De Casteljau's algorithm
http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
Related
My intention is to create a line with canvas that adjusts itself whenever the objects change position, so that it always has a correct shape.
I currently have the following (uses a quadratic curve to make the turn):
I have found a GoJS library that does exactly what I want, which is the following:
The problem is that the library is not open source, so I wanted to know if this is some kind of generic algorithm to do it, or I have to do the calculation manually to identify the positions of the objects and calculate the best trajectory.
As extra information, I am making use of KonvaJS which natively does not implement anything like this.
It depends on how much complexity you need. This is a fiddle that does the basic pathfinding you have in the animation (without rounding the corners).
It figures out what side the paths should start from, and turns that into the offset for the path start and end, by comparing the x and y distances of the two objects:
let startsVertical = Math.abs(dx) < Math.abs(dy);
if (startsVertical) {
anchorPointOffset = {x: 0, y: Math.sign(dy) * circleRadius};
} else {
anchorPointOffset = {x: Math.sign(dx) * circleRadius, y: 0};
}
let stationaryAnchorPoint = {
x: stationaryPosition.x + anchorPointOffset.x,
y: stationaryPosition.y + anchorPointOffset.y
};
let movingAnchorPoint = {
x: movingPosition.x - anchorPointOffset.x,
y: movingPosition.y - anchorPointOffset.y
};
If your shapes do not have the same width and height, you will need to use two variables, instead of circleRadius.
Then it calculates the center point and uses that to create the middle two points of the path.
if (startsVertical) {
middleA = {
x: stationaryAnchorPoint.x,
y: centerPoint.y
}
middleB = {
x: movingAnchorPoint.x,
y: centerPoint.y
}
} else {
middleA = {
x: centerPoint.x,
y: stationaryAnchorPoint.y
}
middleB = {
x: centerPoint.x,
y: movingAnchorPoint.y
}
}
Rounding the corners is a little more complicated, but there are many guides for that.
I have an array of objects. Every object represents a square that's being drawn on the screen - x/y for placement and s for size, c for color).
const elements = [
{ x: 0, y: 0, s: 20, c: 'red' },
{ x: 110, y: 55, s: 7, c: 'blue' },
{ x: 250, y: 250, s: 50, c: 'green' },
{ x: 400, y: 400, s: 30, c: 'pink' }
]
That's how those would look on a canvas or just the page (doesn't have to be canvas really):
Now Imagine I have a 25x25px black square that is rendered instead of my cursor. When I move cursor over one of the colourful squares - so the square is fully covered - my pointer "eats" them up, so they disappear from the array and the canvas. Just like eating food in good old snake!
const pointer = { x: event.pageX, y: event.pageY, s: 25, c: 'black' }
So doing this:
Would remove elements[1] aka { x: 110, y: 55, s: 7, c: 'blue' }. As my cursor covers the whole blue square. I can't obviously eat up the green square as it's bigger than my cursor.
My question is - what's the best algorithm to find what items in my elements array are fully covered by the cursor considering I could have a lot of colourful squares (let's say over 1000)?
I've been trying to filter the covered item like so:
let squareCovered = elements.filter(square => square.x == pointer.x && square.y == pointer.y);
But this is not good enough as does not take both squares and cursors sizes, so I always have to put the cursor exactly at the very center of the square. When I'm trying to introduce sizes in this filtering method my project gets really laggy very fast.
Any hints? Is there a performant algorithm for this?
Feel free to edit the question title, no idea what I'm actually asking for.
Touching the DOM is the expensive part; you'll be able to brute-force this up to a surprisingly large number of squares if you work as much as possible against the source data instead of the rendered page.
The only DOM manipulation below, other than initializing the layout, is repositioning the black square on mouse move and removing the "eaten" elements; looping through the elements array is much faster than looping through them as DOM nodes.
Here's a demo with 10,000 squares; it starts to get laggy at about 25,000 squares:
const colors = ['red', 'blue', 'green', 'violet', 'indigo', 'pink', 'orange', 'bisque', 'chocolate', 'gold', 'fuschia', 'firebrick', 'peru'];
// make a lot of squares.
const elements = []
for (let i = 0; i < 10000; i++) {
elements.push({
x: Math.floor(Math.random() * window.innerWidth),
y: Math.floor(Math.random() * window.innerHeight),
s: Math.floor(Math.random() * 10), // keeping them small or the screen gets too crowded with uneatable squares
c: colors[Math.floor(Math.random() * colors.length)]
});
}
// quick and dirty way to draw the squares:
let squaresHTML = "";
for (let i = 0; i < elements.length; i++) {
let el = elements[i];
squaresHTML += `<div id="square${i}" style="position:absolute; left:${el.x}px; top: ${el.y}px; width: ${el.s}px; height: ${el.s}px; background-color: ${el.c}"></div>`;
}
// This is *much* faster than a lot of createElement() appendChild() stuff
document.getElementById('container').innerHTML = squaresHTML;
// handle mousemove:
const cursor = document.getElementById('cursor');
const apothem = cursor.clientWidth / 2;
document.body.addEventListener('mousemove', e => {
// position the black square:
[cursor.style.left, cursor.style.top] = [`${e.clientX - apothem}px`, `${e.clientY - apothem}px`];
// brute force search for overlaps:
for (let i = 0; i < elements.length; i++) {
let el = elements[i];
if (
(el.x >= e.clientX - apothem) &&
(el.y >= e.clientY - apothem) &&
(el.x + el.s <= e.clientX + apothem) &&
(el.y + el.s <= e.clientY + apothem)
) {
// found a sqare that is completely covered; "eat" it (if we haven't already):
if (document.getElementById(`square${i}`)) {
document.getElementById(`square${i}`).remove()
// Do not modify the elements array here! The indexes are hardcoded!
}
}
}
})
body {
overflow: hidden
}
#container {
width: 100vw;
height: 100vh
}
#cursor {
width: 25px;
height: 25px;
position: absolute;
background-color: black
}
<div id="container"></div>
<div id="cursor"></div>
If I had to further optimize this, I'd probably break the page up into "zones" and precalculate which squares are in which "zone"; that way when the mouse moves you can quickly identify which subset of elements you need to iterate over. (Squares that overlap zone boundaries would add some complexity but you could work around that by either duplicating the squares’ entries for each relevant zone, or basing both their zone and the “which zone am I searching” on anything consistent, say, the squares’ top left pixel.)
I am creating the ground of a game using a Perlin noise function. This gives me an array of vertices. I then add a vertex at the front that is {x:0 y: WORLD_HEIGHT} and another at the end of the array that is {x: WORLD_WIDTH y: WORLD_HEIGHT}. I am hoping that will give me a flat base with a random top.
How then do I add this into the matter.js world?
I am trying to create the ground using;
var terrain = Bodies.fromVertices(???, ???, vertexSets, {
isStatic: true
}, true);
but I don't know what to use for the ??? co-ordinates. I think they are supposed to represent the center of the object. However, I don't know what that is because it is noise. What I would like to do is specify the x & y of the first perlin noise vertex.
I am not even sure that given these vertices matter.js is creating a single body or multiple.
Is this the right way to approach it or there another way to do this? I am really struggling with the docs and the examples.
I use Matter.Body.setPosition(body, position) to override the center of mass and put the ground where I want it based on its bounds property.
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
});
const w = 300;
const h = 300;
const vertices = [
...[...Array(16)].map((_, i) => ({
x: i * 20,
y: ~~(Math.random() * 40),
})),
{x: w, y: 100},
{x: 0, y: 100},
];
const ground = Matter.Bodies.fromVertices(
w - 10, h - 10, // offset by 10 pixels for illustration
vertices,
{isStatic: true},
/* flagInternal =*/ true,
);
Matter.Body.setPosition(ground, {
x: w - ground.bounds.min.x,
y: h - ground.bounds.max.y + 110,
});
const {min: {x}, max: {y}} = ground.bounds;
console.log(x, y); // 10 120
Matter.Composite.add(engine.world, [ground]);
Matter.Render.run(render);
Matter.Runner.run(engine);
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
Without setPosition, you can see things jump around if you run this snippet a few times (just to reproduce OP's error with a concrete example):
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
});
const vertices = [
...[...Array(16)].map((_, i) => ({
x: i * 20,
y: ~~(Math.random() * 40),
})),
{x: 300, y: 100},
{x: 0, y: 100},
];
const ground = Matter.Bodies.fromVertices(
200, 100, vertices,
{isStatic: true},
/* flagInternal =*/ true,
);
Matter.Composite.add(engine.world, [ground]);
Matter.Render.run(render);
Matter.Runner.run(engine);
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
I'm not using Perlin noise and there are some internal vertices that aren't properly detected in the above examples, but the result should be the same either way.
should be integers, all width and height of the noise texture. values at those x, y integer places can be floats... no problem.
and same width and height should go to terrain and values at that places will be the height of the terrain.
First Note: They wont let me embed images until i have more reputation points (sorry), but all the links are images posted on imgur! :) thanks
I have replicated a method to animate any single path (1 closed path) using fourier transforms. This creates an animation of epicylces (rotating circles) which rotate around each other, and follow the imputed points, tracing the path as a continuous loop/function.
I would like to adopt this system to 3D. the two methods i can think of to achieve this is to use a Spherical Coordinate system (two complex planes) or 3 Epicycles --> one for each axis (x,y,z) with their individual parametric equations. This is probably the best way to start!!
2 Cycles, One for X and one for Y:
Picture: One Cycle --> Complex Numbers --> For X and Y
Fourier Transformation Background!!!:
• Eulers formula allows us to decompose each point in the complex plane into an angle (the argument to the exponential function) and an amplitude (Cn coefficients)
• In this sense, there is a connection to imaging each term in the infinite series above as representing a point on a circle with radius cn, offset by 2πnt/T radians
• The image below shows how a sum of complex numbers in terms of phases/amplitudes can be visualized as a set of concatenated cirlces in the complex plane. Each red line is a vector representing a term in the sequence of sums: cne2πi(nT)t
• Adding the summands corresponds to simply concatenating each of these red vectors in complex space:
Animated Rotating Circles:
Circles to Animated Drawings:
• If you have a line drawing in 2D (x-y) space, you can describe this path mathematically as a parametric function. (two separate single variable functions, both in terms of an auxiliary variable (T in this case):
• For example, below is a simple line drawing of a horse, and a parametric path through the black pixels in image, and that path then seperated into its X and Y components:
• At this point, we need to calculate the Fourier approximations of these two paths, and use coefficients from this approximation to determine the phase and amplitudes of the circles needed for the final visualization.
Python Code:
The python code used for this example can be found here on guithub
I have successful animated this process in 2D, but i would like to adopt this to 3D.
The Following Code Represents Animations in 2D --> something I already have working:
[Using JavaScript & P5.js library]
The Fourier Algorithm (fourier.js):
// a + bi
class Complex {
constructor(a, b) {
this.re = a;
this.im = b;
}
add(c) {
this.re += c.re;
this.im += c.im;
}
mult(c) {
const re = this.re * c.re - this.im * c.im;
const im = this.re * c.im + this.im * c.re;
return new Complex(re, im);
}
}
function dft(x) {
const X = [];
const Values = [];
const N = x.length;
for (let k = 0; k < N; k++) {
let sum = new Complex(0, 0);
for (let n = 0; n < N; n++) {
const phi = (TWO_PI * k * n) / N;
const c = new Complex(cos(phi), -sin(phi));
sum.add(x[n].mult(c));
}
sum.re = sum.re / N;
sum.im = sum.im / N;
let freq = k;
let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
let phase = atan2(sum.im, sum.re);
X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
Values[k] = {phase};
console.log(Values[k]);
}
return X;
}
The Sketch Function/ Animations (Sketch.js):
let x = [];
let fourierX;
let time = 0;
let path = [];
function setup() {
createCanvas(800, 600);
const skip = 1;
for (let i = 0; i < drawing.length; i += skip) {
const c = new Complex(drawing[i].x, drawing[i].y);
x.push(c);
}
fourierX = dft(x);
fourierX.sort((a, b) => b.amp - a.amp);
}
function epicycles(x, y, rotation, fourier) {
for (let i = 0; i < fourier.length; i++) {
let prevx = x;
let prevy = y;
let freq = fourier[i].freq;
let radius = fourier[i].amp;
let phase = fourier[i].phase;
x += radius * cos(freq * time + phase + rotation);
y += radius * sin(freq * time + phase + rotation);
stroke(255, 100);
noFill();
ellipse(prevx, prevy, radius * 2);
stroke(255);
line(prevx, prevy, x, y);
}
return createVector(x, y);
}
function draw() {
background(0);
let v = epicycles(width / 2, height / 2, 0, fourierX);
path.unshift(v);
beginShape();
noFill();
for (let i = 0; i < path.length; i++) {
vertex(path[i].x, path[i].y);
}
endShape();
const dt = TWO_PI / fourierX.length;
time += dt;
And Most Importantly! THE PATH / COORDINATES:
(this one is a triangle)
let drawing = [
{ y: -8.001009734 , x: -50 },
{ y: -7.680969345 , x: -49 },
{ y: -7.360928956 , x: -48 },
{ y: -7.040888566 , x: -47 },
{ y: -6.720848177 , x: -46 },
{ y: -6.400807788 , x: -45 },
{ y: -6.080767398 , x: -44 },
{ y: -5.760727009 , x: -43 },
{ y: -5.440686619 , x: -42 },
{ y: -5.12064623 , x: -41 },
{ y: -4.800605841 , x: -40 },
...
...
{ y: -8.001009734 , x: -47 },
{ y: -8.001009734 , x: -48 },
{ y: -8.001009734 , x: -49 },
];
This answer is in response to: "Do you think [three.js] can replicate what i have in 2D but in 3D? with the rotating circles and stuff?"
Am not sure whether you're looking to learn 3D modeling from scratch (ie, creating your own library of vector routines, homogeneous coordinate transformations, rendering perspective, etc) or whether you're simply looking to produce a final product. In the case of the latter, three.js is a powerful graphics library built on webGL that in my estimation is simple enough for a beginner to dabble with, but has a lot of depth to produce very sophisticated 3D effects. (Peruse the examples at https://threejs.org/examples/ and you'll see for yourself.)
I happen to be working a three.js project of my own, and whipped up a quick example of epicyclic circles as a warm up exercise. This involved pulling pieces and parts from the following references...
https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
https://threejs.org/examples/#misc_controls_orbit
https://threejs.org/examples/#webgl_geometry_shapes (This three.js example is a great resource showing a variety of ways that a shape can be rendered.)
The result is a simple scene with one circle running around the other, permitting mouse controls to orbit around the scene, viewing it from different angles and distances.
<html>
<head>
<title>Epicyclic Circles</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>
<script>
// Set up the basic scene, camera, and lights.
var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
scene.add(camera)
var light = new THREE.PointLight( 0xffffff, 0.8 );
camera.add( light );
camera.position.z = 50;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Add the orbit controls to permit viewing the scene from different angles via the mouse.
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 0;
controls.maxDistance = 500;
// Create center and epicyclic circles, extruding them to give them some depth.
var extrudeSettings = { depth: 2, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: .25, bevelThickness: .25 };
var arcShape1 = new THREE.Shape();
arcShape1.moveTo( 0, 0 );
arcShape1.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath1 = new THREE.Path();
holePath1.moveTo( 0, 10 );
holePath1.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape1.holes.push( holePath1 );
var geometry1 = new THREE.ExtrudeBufferGeometry( arcShape1, extrudeSettings );
var mesh1 = new THREE.Mesh( geometry1, new THREE.MeshPhongMaterial( { color: 0x804000 } ) );
scene.add( mesh1 );
var arcShape2 = new THREE.Shape();
arcShape2.moveTo( 0, 0 );
arcShape2.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath2 = new THREE.Path();
holePath2.moveTo( 0, 10 );
holePath2.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape2.holes.push( holePath2 );
var geometry2 = new THREE.ExtrudeGeometry( arcShape2, extrudeSettings );
var mesh2 = new THREE.Mesh( geometry2, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
scene.add( mesh2 );
// Define variables to hold the current epicyclic radius and current angle.
var mesh2AxisRadius = 30;
var mesh2AxisAngle = 0;
var animate = function () {
requestAnimationFrame( animate );
// During each animation frame, let's rotate the objects on their center axis,
// and also set the position of the epicyclic circle.
mesh1.rotation.z -= 0.02;
mesh2.rotation.z += 0.02;
mesh2AxisAngle += 0.01;
mesh2.position.set ( mesh2AxisRadius * Math.cos(mesh2AxisAngle), mesh2AxisRadius * Math.sin(mesh2AxisAngle), 0 );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Note that I've used basic trigonometry within the animate function to position the epicyclic circle around the center circle, and fudged the rate of rotation for the circles (rather than doing the precise math), but there's probably a better "three.js"-way of doing this via matrices or built in functions. Given that you obviously have a strong math background, I don't think you'll have any issues with translating your 2D model of multi-epicyclic circles using basic trigonometry when porting to 3D.
Hope this helps in your decision making process on how to proceed with a 3D version of your program.
The method that I would suggest is as follows. Start with a parametrized path v(t) = (v_x(t), v_y(t), v_z(t)). Consider the following projection onto the X-Y plane: v1(t) = (v_x(t)/2, v_y(t), 0). And the corresponding projection onto the X-Z plane: v2(t) = (v_x(t)/2, 0, v_z(t)).
When we add these projections together we get the original curve. But each projection is now a closed 2-D curve, and you have solutions for arbitrary closed 2-D curves. So solve each problem. And then interleave them to get a projection where your first circle goes in the X-Y plane, your second one in the X-Z plane, your third one in the X-Y plane, your fourth one in the X-Z plane ... and they sum up to your answer!
I'm using the fabric.js in one project, right now I'm trying to convert a legacy xml array of point to the fabricjs objects.
The legacy project has a xml persistence saved to DB. In C# is a System.Windows.Ink.StrokeCollection using InkPresenter Control in Silverlight
This is the xml generated
https://cloud.githubusercontent.com/assets/20269820/18906832/153537a0-8561-11e6-91ca-6bbbec70d859.png
For fabric.js I tried to create similar prototype, then I have the following:
I check the path array object in fabricjs:
Two objects in fabric js
Is there any way that I could convert that xml structure to the fabric path structure? I could just create a new one using fabric, but I need still to cater for the legacy, in that case I need to find out a way to convert that structure to fabric.
Thank you
For more details: github. com/kangax/fabric.js/issues/3295
The created the solution based on the #AndreaBogazzi suggestion.
var canvas = this.__canvas = new fabric.Canvas('c', {
isDrawingMode: true
});
if (canvas.freeDrawingBrush) {
canvas.freeDrawingBrush.color = "#000000";
canvas.freeDrawingBrush.width = 3;
canvas.freeDrawingBrush.shadowBlur = 0;
}
var pencil = new fabric.PencilBrush(canvas);
var points = [{x: 11, y: 12}, {x: 11, y: 11}, {x: 12, y: 11}];
// Convert Points to SVG Path
var res = pencil.convertPointsToSVGPath(points);
// Create the Path
var path = pencil.createPath(res.toString());
// Set the tickness and color
path.set({ strokeWidth: 3, stroke: "rgba(0, 0, 0, 1)" });
// Add to the Canvas
canvas.add(path);
To revert the path back to Array of Points:
var arr = [];
for (var i = 0; i < canvas.getObjects().length; i++) {
for (var j = 0; j < canvas.getObjects()[i].path.length; j++) {
arr.push({
x: canvas.getObjects()[i].path[j][1],
y: canvas.getObjects()[i].path[j][2]
});
}
}
The fabric.Path structure is a svg Path structure.
I see your structure was a simple array of points.
Imagining you have 4 points (p1, p2, p3, p4) in your drawing archive, the respective command would be:
var pathCommand = "M p1.x p1.y L p2.x p2.y p3.x p3.y p4.x p4.y"
Then you could do new fabric.Path(pathCommand, options) where options include stroke ( your color ) and strokeWidth ( your width ).
That would give you a polyline aspect object.
If you want to mimic the fabric freeDrawingMode with that points you have to check the pencilBrush code you can find here:
http://fabricjs.com/docs/fabric.js.html#line7882
And that can convert an array of points in some quadratic smoother curve.