Connecting quadratic bézier circle in SVG - javascript

I am creating a random SVG "Blob" generator, but can't figure out how to connect the last bézier to the "M" point correctly. In the example you can see a little spike there.
function generate() {
const points = [
{ x: 55.380049480163834, y: 8.141661255952418 },
{ x: 61.89338428790346, y: 59.21935310168805 },
{ x: 6.637386502817552, y: 65.10477483405401 },
{ x: 15.309460889587692, y: 11.231848017862793 }
]
let d = `M ${points[0].x / 2} ${points[0].y}`
d += `Q ${points[0].x} ${points[0].y} ${(points[0].x + points[1].x) * 0.5} ${(points[0].y + points[1].y) * 0.5}`
d += `Q ${points[1].x} ${points[1].y} ${(points[1].x + points[2].x) * 0.5} ${(points[1].y + points[2].y) * 0.5}`
d += `Q ${points[2].x} ${points[2].y} ${(points[2].x + points[3].x) * 0.5} ${(points[2].y + points[3].y) * 0.5}`
d += `Q ${points[3].x} ${points[3].y} ${(points[3].x + points[0].x) * 0.5} ${(points[3].y + points[0].y) * 0.5} Z`
return d
}
document.getElementById('blob').setAttribute('d', generate())
<svg>
<path viewBox="0 0 70 70" id="blob"></path>
</svg>

This is how I would do it: first you need to find the first midpoint (between the last and the first point ) and move to it. next you calculate the midpoint between every 2 points in the points array. Finally you draw the curve through the last point, back to the first midpoint.
const points = [
{ x: 55.380049480163834, y: 8.141661255952418 },
{ x: 61.89338428790346, y: 59.21935310168805 },
{ x: 6.637386502817552, y: 65.10477483405401 },
{ x: 15.309460889587692, y: 11.231848017862793 }
]
function drawCurve(points) {
//find the first midpoint and move to it
var p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
let d = `M${p.x}, ${p.y}`;
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
var mp = {}
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
d += `Q${points[i].x},${points[i].y},${mp.x},${mp.y}`
}
//curve through the last point, back to the first midpoint
d+= `Q${points[points.length - 1].x},${points[points.length - 1].y},${p.x},${p.y}`
blob.setAttributeNS(null,"d",d)
}
drawCurve(points);
svg{border:1px solid; fill:none; stroke:black; width:300px;}
<svg viewBox="0 0 70 70" >
<path id="blob"></path>
</svg>

Related

JS SVG: get path from SVG polgon

I have the following SVG element which was created using JS: https://akzhy.com/blog/create-animated-donut-chart-using-svg-and-javascript
<div class="doughnut">
<svg width="100%" height="100%" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="30" stroke="#80e080" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="141.372" transform='rotate(-90 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#4fc3f7" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="103.6728" transform='rotate(0 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#9575cd" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="169.6464" transform='rotate(162 50 50)'/>
<circle cx="50" cy="50" r="30" stroke="#f06292" stroke-width="15" fill="transparent" stroke-dasharray="188.496" stroke-dashoffset="150.7968" transform='rotate(198 50 50)'/>
</svg>
</div>
Is it possible to to get a path from the svg node?
Conversion via graphic app
Open you svg in an application like Illustrator/inkscape etc.
You could use path operations like "stroke-to-path" to convert stroke based chart segments (the visual colored segments are just dashed strokes applied to a full circle).
Use a pie/donut generator script returning solid paths
Based on this answer by #ray hatfield Pie chart using circle element you can calculate d properties based on the arc command.
Example json based pie chart generator
let pies = document.querySelectorAll('.pie-generate');
generatePies(pies);
function generatePies(pies) {
if (pies.length) {
pies.forEach(function(pie, i) {
let data = pie.getAttribute('data-pie');
if (data) {
data = JSON.parse(data);
w = data['width'];
h = data['height'];
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
svg.setAttribute('width', w);
svg.setAttribute('height', h);
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
pie.appendChild(svg);
addSegments(svg, data);
}
})
}
}
function addSegments(svg, data) {
let segments = data["segments"];
let strokeWidth = data["strokeWidth"];
let centerX = data["centerX"];
let centerY = data["centerY"];
let radius = data["radius"];
let startingAngle = (data["startingAngle"] || data["startingAngle"] == 0) ? data["startingAngle"] : -90;
let gap = data["gap"];
let decimals = data["decimals"];
let offset = 0;
let output = "";
// calculate auto percentages
let total = 0;
let calc = data["calc"] ? true : false;
if (calc) {
segments.forEach(function(segment, i) {
total += segment[0];
});
}
// prevent too large gaps
let circumference = Math.PI * radius * 2;
let circumferencePerc = (circumference / 100);
let gapPercentOuter = 100 / circumference * gap;
if (gapPercentOuter > circumferencePerc) {
gap = gap / (gapPercentOuter / circumferencePerc)
}
segments.forEach(function(segment, i) {
let percent = segment[0];
// calc percentages
let percentCalc = percent.toString().indexOf('/') != -1 ? segment[0].split('/') : [];
percent = percentCalc.length ? percentCalc[0] / percentCalc[1] * 100 : +percent
// calculate auto percentages to get 100% in total
if (total) {
percent = 100 / total * percent;
}
let percentRound = percent.toFixed(decimals);
// auto fill color
let segOptions = segment[1] ? segment[1] : '';
let fill = segOptions ? 'fill="' + segOptions['color'] + '"' : "";
if (!fill) {
let hueCut = 0;
let hueShift = 0;
let hue = Math.abs((360 - hueCut) / 100 * (offset + percent)) + hueShift;
let autoColor = hslToHex(hue.toFixed(0) * 1, 60, 50);
fill = 'fill="' + autoColor + '"';
}
let className = segOptions['class'] ? segOptions['class'] : "";
let classPercent = percentRound.toString().replaceAll('.', '_');
let id = segOptions['id'] ? 'id="' + segOptions['id'] + '" ' : '';
let d = getArcD(centerX, centerY, strokeWidth, offset, percent, radius, gap, decimals, startingAngle);
output +=
`\n<path d="${d}" ${fill} class="segment segment-${classPercent} segment-${(i+1)} ${className}" ${id} data-percent="${percentRound}"/>`;
offset += percent;
});
svg.innerHTML = output;
}
function getArcD(centerX, centerY, strokeWidth, percentStart, percent, radiusOuter, gap, decimals = 3, startingAngle = -90) {
let radiusInner = radiusOuter - strokeWidth;
let circumference = Math.PI * radiusOuter * 2;
let isPieChart = false;
// if pie chart – stroke equals radius
if (strokeWidth + gap >= radiusOuter) {
isPieChart = true;
}
let circumferenceInner = Math.PI * radiusInner * 2;
let gapPercentOuter = ((100 / circumference) * gap) / 2;
let gapPercentInner = ((100 / circumferenceInner) * gap) / 2;
//add offset from previous segments
percentStart = percentStart;
let percentEnd = percent + percentStart;
// outer coordinates
let [x1, y1] = getPosOnCircle(centerX, centerY, (percentStart + gapPercentOuter), radiusOuter, decimals, startingAngle);
let [x2, y2] = getPosOnCircle(centerX, centerY, percentEnd - gapPercentOuter, radiusOuter, decimals, startingAngle);
// switch arc output between long or short arc segment according to percentage
let longArc = percent >= 50 ? 1 : 0;
let rotation = 0;
let clockwise = 1;
let counterclockwise = 0;
let d = '';
// if donut chart
if (!isPieChart) {
//inner coordinates
let [x3, y3] = getPosOnCircle(centerX, centerY, percentEnd - gapPercentInner, radiusInner, decimals, startingAngle);
let [x4, y4] = getPosOnCircle(centerX, centerY, percentStart + gapPercentInner, radiusInner, decimals, startingAngle);
d = [
"M", x1, y1,
"A", radiusOuter, radiusOuter, rotation, longArc, clockwise, x2, y2,
"L", x3, y3,
"A", radiusInner, radiusInner, rotation, longArc, counterclockwise, x4, y4,
"z"
];
}
// if pie chart – stroke equals radius: drop inner radius arc
else {
// find opposite coordinates
let [x1o, y1o] = getPosOnCircle(centerX, centerY, (percentStart - gapPercentOuter) - 50, radiusOuter, decimals, startingAngle);
let [x2o, y2o] = getPosOnCircle(centerX, centerY, (percentEnd + gapPercentOuter) - 50, radiusOuter, decimals, startingAngle);
let extrapolatedIntersection = getLinesIntersection(
[x1, y1, x1o, y1o], [x2, y2, x2o, y2o],
decimals);
d = [
"M", x1, y1,
"A", radiusOuter, radiusOuter, rotation, longArc, clockwise, x2, y2,
"L", extrapolatedIntersection.join(" "),
"z"
];
}
return d.join(" ");
}
// helper: get x/y coordinates according to angle percentage
function getPosOnCircle(centerX, centerY, percent, radius, decimals = 3, angleOffset = -90) {
let angle = 360 / (100 / percent) + angleOffset;
let x = +(centerX + Math.cos((angle * Math.PI) / 180) * radius).toFixed(
decimals
);
let y = +(centerY + Math.sin((angle * Math.PI) / 180) * radius).toFixed(
decimals
);
return [x, y];
}
// helper: get intersection coordinates
function getLinesIntersection(l1, l2, decimals = 3) {
let intersection = [];
let c2x = l2[0] - l2[2];
let c3x = l1[0] - l1[2];
let c2y = l2[1] - l2[3];
let c3y = l1[1] - l1[3];
// down part of intersection point formula
let d = c3x * c2y - c3y * c2x;
if (d != 0) {
// upper part of intersection point formula
let u1 = l1[0] * l1[3] - l1[1] * l1[2];
let u4 = l2[0] * l2[3] - l2[1] * l2[2];
// intersection point formula
let px = +((u1 * c2x - c3x * u4) / d).toFixed(decimals);
let py = +((u1 * c2y - c3y * u4) / d).toFixed(decimals);
intersection = [px, py];
}
return intersection;
}
function hslToHex(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}`;
}
<div class="pie-generate" data-pie='{
"width": 100,
"height": 100,
"radius": 50,
"centerX": 50,
"centerY": 50,
"strokeWidth": 20,
"gap": 0,
"decimals": 3,
"segments": [
["25", {"color":"#80e080", "id":"seg01", "class":"segCustom"}],
["45", {"color":"#4fc3f7", "id":"seg02", "class":"segCustom"}],
["10", {"color":"#9575cd", "id":"seg03", "class":"segCustom"}],
["20", {"color":"#f06292", "id":"seg04", "class":"segCustom"}]
]
}'>
</div>
You can tweak different segment percentages by changing the JSOn data-attribute.
<div class="pie-generate" data-pie='{
"width": 100,
"height": 100,
"radius": 50,
"centerX": 50,
"centerY": 50,
"strokeWidth": 20,
"gap": 0,
"decimals": 3,
"segments": [
["25", {"color":"#80e080", "id":"seg01", "class":"segCustom"}],
["45", {"color":"#4fc3f7", "id":"seg02", "class":"segCustom"}],
["10", {"color":"#9575cd", "id":"seg03", "class":"segCustom"}],
["20", {"color":"#f06292", "id":"seg04", "class":"segCustom"}]
]
}'>
</div>
Segment output:
<path d="M 50 0 A 50 50 0 0 1 100 50 L 80 50 A 30 30 0 0 0 50 20 z" fill="#80e080" />

How to convert TCanvas->Arc value to SVG Arc

I write a converter for my Company from Metafile to SVG (TCanvas->arc).
I already finished to convert rectangle or some other elements but i dont get it how i can convert the arc.
I write my Code in JavaScript. :)
I have a file and i read it in buffer and get the values but that is uninteresting for you.
So we currently have all the values I can get:
Point1,Point2,Start,End
These 4 points are given and from this I should draw an arc now
dc->Arc (Point1.x + offset->x,
Point1.y + offset->y,
Point2.x + offset->x,
Point2.y + offset->y,
Start.x + offset->x,
Start.y + offset->y,
Ende.x + offset->x,
Ende.y + offset->y);
They are currently drawing the arc with this command. You can not pay attention to the offset here.
How can i get all Informations from my given points to draw in Arc in SVG.
for Example real values:
Point1: -50, -6
Point2: -10, 34
Start: -10, 34
End: -10, -6
or
Point1: 1, 18
Point2: 41, 58
Start: 1, 18
End: 1, 58
How do I get to the: large-arc-flag, sweep-flag and rotation and what values do I have to use or calculate that it is drawn correctly.
I tried to draw it and looked at a lot of documentation and tried to create it in writing.
I've whipped up something that seems to work. It's based on the documentation here.
I haven't tested it exhaustively.
I've made the assumption that, in a TCanvas, (0,0) is at the top. If it isn't, you'll need to reverse the logic of the sweep and large arc flags.
var svg = document.querySelector("svg");
var debug = svg.getElementById("debug");
function arc(x1, y1, x2, y2, x3, y3, x4, y4)
{
let xRadius = Math.abs(x2 - x1) / 2;
let yRadius = Math.abs(y2 - y1) / 2;
let xCentre = Math.min(x1, x2) + xRadius;
let yCentre = Math.min(y1, y2) + yRadius;
// get intercepts relative to ellipse centre
let startpt = interceptEllipseAndLine(xRadius, yRadius, x3 - xCentre, y3 - yCentre);
let endpt = interceptEllipseAndLine(xRadius, yRadius, x4 - xCentre, y4 - yCentre);
let largeArcFlag = isLargeArc(startpt, endpt) ? 1 : 0;
return ['M', xCentre + startpt.x, yCentre + startpt.y,
'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y].join(' ');
}
// Finds the intercept of an ellipse and a line from centre to x0,y0
function interceptEllipseAndLine(xRadius, yRadius, x0,y0)
{
let den = Math.sqrt(xRadius * xRadius * y0 * y0 + yRadius * yRadius * x0 * x0);
let mult = xRadius * yRadius / den;
return {x: mult * x0, y: mult * y0};
}
// Returns true if the angle between the two intercept lines is >= 180deg
function isLargeArc(start, end)
{
let angle = Math.atan2(start.x * end.y - start.y * end.x, start.x * end.x + start.y * end.y);
return angle > 0;
}
let path1 = svg.getElementById("path1");
path1.setAttribute("d", arc(1, 18, 41, 58, 1, 18, 1, 58) );
let path2 = svg.getElementById("path2");
path2.setAttribute("d", arc(-50, -6, -10, 34, -10, 34, -10, -6) );
svg {
width: 400px;
}
path {
fill: none;
stroke: red;
stroke-width: 1px;
}
<svg viewBox="-100 -100 200 200">
<path id="path1"/>
<path id="path2"/>
</svg>
And here's a version that adds some extra shapes for debugging purposes...
var svg = document.querySelector("svg");
var debug = svg.getElementById("debug");
function arc(x1, y1, x2, y2, x3, y3, x4, y4)
{
let xRadius = Math.abs(x2 - x1) / 2;
let yRadius = Math.abs(y2 - y1) / 2;
let xCentre = Math.min(x1, x2) + xRadius;
let yCentre = Math.min(y1, y2) + yRadius;
{
let rect = document.createElementNS(svg.namespaceURI, "rect");
rect.setAttribute("x", x1);
rect.setAttribute("y", y1);
rect.setAttribute("width", x2-x1);
rect.setAttribute("height", y2-y1);
debug.append(rect);
let ellipse = document.createElementNS(svg.namespaceURI, "ellipse");
ellipse.setAttribute("cx", xCentre);
ellipse.setAttribute("cy", yCentre);
ellipse.setAttribute("rx", xRadius);
ellipse.setAttribute("ry", yRadius);
debug.append(ellipse);
let start = document.createElementNS(svg.namespaceURI, "line");
start.setAttribute("x1", xCentre);
start.setAttribute("y1", yCentre);
start.setAttribute("x2", x3);
start.setAttribute("y2", y3);
debug.append(start);
let end = document.createElementNS(svg.namespaceURI, "line");
end.setAttribute("x1", xCentre);
end.setAttribute("y1", yCentre);
end.setAttribute("x2", x4);
end.setAttribute("y2", y4);
debug.append(end);
}
// get intercepts relative to ellipse centre
let startpt = interceptEllipseAndLine(xRadius, yRadius, x3 - xCentre, y3 - yCentre);
let endpt = interceptEllipseAndLine(xRadius, yRadius, x4 - xCentre, y4 - yCentre);
let largeArcFlag = isLargeArc(startpt, endpt) ? 1 : 0;
{
let circ = document.createElementNS(svg.namespaceURI, "circle");
circ.setAttribute("cx", xCentre + startpt.x);
circ.setAttribute("cy", yCentre + startpt.y);
circ.setAttribute("r", 1);
debug.append(circ);
}
return ['M', xCentre + startpt.x, yCentre + startpt.y,
'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y].join(' ');
}
// Finds the intercept of an ellipse and a line from centre to x0,y0
function interceptEllipseAndLine(xRadius, yRadius, x0,y0)
{
let den = Math.sqrt(xRadius * xRadius * y0 * y0 + yRadius * yRadius * x0 * x0);
let mult = xRadius * yRadius / den;
return {x: mult * x0, y: mult * y0};
}
// Returns true if the angle between the two intercept lines is >= 180deg
function isLargeArc(start, end)
{
let angle = Math.atan2(start.x * end.y - start.y * end.x, start.x * end.x + start.y * end.y);
return angle > 0;
}
let path1 = svg.getElementById("path1");
path1.setAttribute("d", arc(1, 18, 41, 58, 1, 18, 1, 58) );
let path2 = svg.getElementById("path2");
path2.setAttribute("d", arc(-50, -6, -10, 34, -10, 34, -10, -6) );
svg {
width: 400px;
}
ellipse, rect, line {
fill: none;
stroke: lightgrey;
stroke-width: 0.5px;
}
path {
fill: none;
stroke: red;
stroke-width: 1px;
}
<svg viewBox="-100 -100 200 200">
<g id="debug"></g>
<path id="path1"/>
<path id="path2"/>
</svg>
Update: Pie
For the Pie function, it should be almost identical to arc() but it will return a slightly different path.
function pie(x1, y1, x2, y2, x3, y3, x4, y4)
{
// ... rest of function is the same as arc() ...
return ['M', xCentre, yCentre,
'L', xCentre + startpt.x, yCentre + startpt.y,
'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y,
'Z'].join(' ');
}

How can I create an array of X,Y coordinates in a square shape given three inputs?

I am trying to create a grid of x/y coordinates in a square pattern, given three points on an x/y plane. A grid like this
This is to be used to drive a gcode generator for moving a tool-head to desired x,y positions on a 3d printer. I need to account for skew and off-alignment of the square, so the grid of x / y points inside the square needs to account for the alignment.
function genGrid (topLeft, btmRight, btmLeft, rows, cols) {
// Generate Grid
// Return array of coordinates like the red dots in the picture I made.
}
[This picture helps explain it better!]
This code did the trick!
<script>
function grid(p1, p2, count) {
pointPerRow = Math.sqrt(count);
p3 = {
x: (p1.x + p2.x + p2.y - p1.y) / 2,
y: (p1.y + p2.y + p1.x - p2.x) / 2
};
p4 = {
x: (p1.x + p2.x + p1.y - p2.y) / 2,
y: (p1.y + p2.y + p2.x - p1.x) / 2
};
edgeLenght = Math.sqrt( (p3.x - p1.x)**2 + (p3.y - p1.y)**2);
vectorH = {
x: (p3.x - p1.x) / edgeLenght,
y: (p3.y - p1.y) / edgeLenght
};
vectorV = {
x: (p4.x - p1.x) / edgeLenght,
y: (p4.y - p1.y) / edgeLenght
};
movingStep = edgeLenght / (pointPerRow -1);
result = {};
for (var i = 0; i < pointPerRow; i++) {
row = {};
point = {
x: p1.x + vectorH.x * movingStep * (i),
y: p1.y + vectorH.y * movingStep * (i),
}
for (var j = 0; j < pointPerRow; j++) {
row[j] = {
x: point.x + vectorV.x * movingStep * (j),
y: point.y + vectorV.y * movingStep * (j),
};
}
result[i] = row;
}
// Debugging
for (var x=0;x < pointPerRow; x++) {
for (var y=0; y < pointPerRow; y++) {
ctx.fillStyle="#000000";
ctx.fillRect(result[x][y].x,result[x][y].y,10,10);
}
}
ctx.fillStyle="#FF0000";
ctx.fillRect(p1.x,p1.y,5,5);
ctx.fillRect(p2.x,p2.y,5,5);
ctx.fillRect(p3.x,p3.y,5,5);
ctx.fillRect(p4.x,p4.y,5,5);
return result;
}
// Create a canvas that extends the entire screen
// and it will draw right over the other html elements, like buttons, etc
var canvas = document.createElement("canvas");
canvas.setAttribute("width", window.innerWidth);
canvas.setAttribute("height", window.innerHeight);
canvas.setAttribute("style", "position: absolute; x:0; y:0;");
document.body.appendChild(canvas);
//Then you can draw a point at (10,10) like this:
var ctx = canvas.getContext("2d");
var grid = grid({x:100, y:50}, {x:200, y:350}, 16);
</script>

Draw circle svg orthogonal projections

I need to get the svg path of a circle projected in a orthogonal space.
Example
What I want to do is create a function(in js) that has the following parameters:
the position of the circle
the radius
and what panel is the circle parallel to
axes inclination
This is the function I use to create a simple circle (without perspective)
function getPath(cx,cy,r){
return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}
I don't want to approximate the circle creating thousands of points and projecting them all, I want to have a path the accurately describes the projected circle
What can I do?
I'm pulling something from an unpublished project and hope it makes sense for you.
Suppose you have two tupples of three points, describing two triangles, find the transform matrix between the two. - They could describe the square enclosing a circle, like this:
Generate the transformation matrix from two point lists:
var source = [s0, s1, s2]; // each point as coordinates {x, y}
var target = [t0, t1, t2];
function generate (source, target) {
var transform = [
{
a: 1, b: 0, c: 0, d: 1,
e: target[2].x,
f: target[2].y
},
{
a: 1, b: 0, c: 0, d: 1,
e: -source[2].x,
f: -source[2].y
}
];
source.forEach(point => {x: point.x - source[2].x, y: point.y - source[2].y});
target.forEach(point => {x: point.x - source[2].x, y: point.y - source[2].y});
var div = source[0].x * source[1].y - source[1].x * source[0].y;
var matrix = {
a: (target[0].x * source[1].y - target[1].x * source[0].y) / div,
b: (target[0].y * source[1].y - target[1].y * source[0].y) / div,
c: (target[1].x * source[0].x - target[0].x * source[1].x) / div,
d: (target[1].y * source[0].x - target[0].y * source[1].x) / div,
e: 0,
f: 0
};
transform.splice(1, 0, matrix);
return transform.reduce(function (m1, m2) {
return {
a: m1.a * m2.a + m1.c * m2.b,
b: m1.b * m2.a + m1.d * m2.b,
c: m1.a * m2.c + m1.c * m2.d,
d: m1.b * m2.c + m1.d * m2.d,
e: m1.a * m2.e + m1.c * m2.f + m1.e,
f: m1.b * m2.e + m1.d * m2.f + m1.f
}
}, { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
}
Now, if you have an absolute arc command described as an object arc
{ rx, ry, rotation, large, sweep, x, y }
the transformation could be applied like this:
function arc_transform (transform, arc) {
var co = Math.cos(arc.rotation/180*Math.PI),
si = Math.sin(arc.rotation/180*Math.PI);
var m = [
arc.rx * (transform.a * co + transform.c * si),
arc.rx * (transform.b * co + transform.d * si),
arc.ry * (transform.c * co - transform.a * si),
arc.ry * (transform.d * co - transform.b * si),
];
var A = (m[0] * m[0]) + (m[2] * m[2]),
B = 2 * (m[0] * m[1] + m[2] * m[3]),
C = (m[1] * m[1]) + (m[3] * m[3]),
K = Math.sqrt((A - C) * (A - C) + B * B);
if ((transform.a * transform.d) - (transform.b * transform.c) < 0) {
arc.sweep = !arc.sweep;
}
return {
rx: Math.sqrt(0.5 * (A + C + K)),
ry: Math.sqrt(0.5 * Math.max(0, A + C - K)),
rotation: Math.abs((A - C) / B) < 1e-6 ? 90 : Math.atan2(B, A - C)*90/Math.PI,
large: arc.large,
sweep: arc.sweep,
x: transform.a * arc.x + transform.c * arc.y + transform.e,
y: transform.b * arc.x + transform.d * arc.y + transform.f
};
};

Inverse kinematics with an arbitrary number of points

I've modified this example of inverse kinematics in JavaScript with HTML5 Canvas and made it dynamic by seperating it into a function, and it works, but the example only uses 3 points -- start, middle, and end, and I'd like to change the number of points at will. Here's my current fiddle...
function _kinematics(joints, fx, mouse) {
joints.forEach(function (joint) {
joint.target = joint.target || {
x: fx.canvas.width / 2,
y: fx.canvas.height / 2
};
joint.start = joint.start || {
x: 0,
y: 0
};
joint.middle = joint.middle || {
x: 0,
y: 0
};
joint.end = joint.end || {
x: 0,
y: 0
};
joint.length = joint.length || 50;
});
var theta,
$theta,
_theta,
dx,
dy,
distance;
joints.forEach(function (joint) {
if (mouse) {
joint.target.x = mouse.x;
joint.target.y = mouse.y;
}
dx = joint.target.x - joint.start.x;
dy = joint.target.y - joint.start.y;
distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
_theta = Math.atan2(dy, dx);
if (distance < joint.length) {
theta = Math.acos(distance / (joint.length + joint.length)) + _theta;
dx = dx - joint.length * Math.cos(theta);
dy = dy - joint.length * Math.sin(theta);
$theta = Math.atan2(dy, dx);
} else {
theta = $theta = _theta;
}
joint.middle.x = joint.start.x + Math.cos(theta) * joint.length;
joint.middle.y = joint.start.y + Math.sin(theta) * joint.length;
joint.end.x = joint.middle.x + Math.cos($theta) * joint.length;
joint.end.y = joint.middle.y + Math.sin($theta) * joint.length;
fx.beginPath();
fx.moveTo(joint.start.x, joint.start.y);
/* for (var i = 0; i < joint.points.length / 2; i++) {
fx.lineTo(joint.points[i].x, joint.points[i].y);
} */
fx.lineTo(joint.middle.x, joint.middle.y);
/* for (var j = joint.points.length / 2; j < joint.points.length; j++) {
fx.lineTo(joint.points[j].x, joint.points[j].y);
} */
fx.lineTo(joint.end.x, joint.end.y);
fx.strokeStyle = "rgba(0,0,0,0.5)";
fx.stroke();
fx.beginPath();
fx.arc(joint.start.x, joint.start.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(255,0,0,0.5)";
fx.fill();
fx.beginPath();
fx.arc(joint.middle.x, joint.middle.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(0,255,0,0.5)";
fx.fill();
fx.beginPath();
fx.arc(joint.end.x, joint.end.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(0,0,255,0.5)";
fx.fill();
});
}
That's just the function, I've omitted the rest for brevity. As you can see, the commented-out lines were my attempt to draw the other points.
Also, here's where I populate the joints array with the points and such. See commented lines.
populate(_joints, $joints, function() {
var coords = randCoords(map);
var o = {
start: {
x: coords.x,
y: coords.y
},
// points: [],
target: {
x: mouse.x,
y: mouse.y
}
};
/* for (var p = 0; p < 10; p++) {
o.points.push({
x: p === 0 ? o.start.x + (o.length || 50) : o.points[p - 1].x + (o.length || 50),
y: p === 0 ? o.start.y + (o.length || 50) : o.points[p - 1].y + (o.length || 50)
});
}; */
return o;
});
How would I make this function work with n points?

Categories