I need help with the solution provided here.
Create easy function 40% off set
I need to modify it so that the returned left and rights are extrapolated to 1,1 after splitting. This is because if I don't extrapolate, I can't use the returned split cubic-bezier as a css transition.
So this is the test I did. Please help because real does not match mike way :( I think the issue is I need to extrapolate the result to 1,1. I can't simply double the values though I'm pretty sure.
REAL
ease-in-out is cubic-bezier(.42,0,.58,1) and graphically is http://cubic-bezier.com/#.42,0,.58,1
first half is ease-in which is cubic-bezier(.42,0,1,1) and graphically is http://cubic-bezier.com/#.42,0,1,1
seoncd half is ease-out which is cubic-bezier(0,0,.58,1) and grpahically is http://cubic-bezier.com/#0,0,.58,1
The function posted above returns the following
ease-in-out is same as this is starting point
first half, left, is given to be cubic-bezier(0.21, 0, 0.355, 0.25) and graphically is http://cubic-bezier.com/#.21,0,.35,.25
code returned: left:[0, 0, 0.21, 0, 0.355, 0.25, 0.5, 0.5]
second half, right, is given to be cubic-bezier(0.645, 0.75, 0.79, 1) and graphically is http://cubic-bezier.com/#.64,.75,.79,1
code returned right:[0.5, 0.5, 0.645, 0.75, 0.79, 1, 1, 1]
Code used for getting it the Mike way is this:
var result = split({
z: .5,
x: [0, 0.42, 0.58, 1],
y: [0, 0, 1, 1]
});
alert(result.toSource());
first half is ease-in which is cubic-bezier(.42,0,1,1) and graphically is http://cubic-bezier.com/#.42,0,1,1
Please verify this assumption. (curves original end points are 0,0, and 1,1 in a css timing function)
The first half of the bezier curve [0,0, .42,0, .58,1, 1,1] should not be [0,0 .42,0, 1,1, 1,1]
The end points are correct (after scaling to 1,1), but you have lost continuity there.
The values returned by Mike's algorithm is correct.
Try this visualisation for an explanation on why your assumption might be wrong.
The algorithm you are using to split is a well known algorithm called de Casteljau algorithm. This method can be geometrically expressed in a very simple manner. Check out the animated visualisations on how this splitting at any arbitrary point is possible https://en.wikipedia.org/wiki/B%C3%A9zier_curve.
However, You may soon hit an issue trying to correctly scale a split portion of a bezier curve to fit in a unit square, with endpoints fixed at 0,0 and 1,1. This probably you can try out quite easily on paper. The easiest way probably is to just linearly scale the control points of the bezier, you will get a squashed curve in most cases though.
I created a modified version of Mike's split function so it fits it to a unit square :) It uses hkrish's pointers to do coordinate normalization.
Just set parameter fitUnitCell to true. :)
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
var easeInOut = {
xs: [0, .42, .58, 1],
ys: [0, 0, 1, 1]
};
var splitRes = splitCubicBezier({
z: .5,
x: easeInOut.xs,
y: easeInOut.ys,
fitUnitSquare: false
});
alert(splitRes.toSource())
Related
You have two triangles a1 b1 c1 and a2 b2 c3 on a plane. Your task is to determine whether they are, i.e. if their corresponding angles have the same measurements.
coordinates is an
array []
let coord = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0];
where a1 is (coord[0],coord[1]), b1 (coord[2],coord[3]) ...
let s = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0]
function areTrianglesSimilar(c) {
let result = null
let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1]))
let line2 = (Math.abs(c[4]) - Math.abs(c[0])) + (Math.abs(c[5]) - Math.abs(c[1]))
let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2)))
console.log(line1, line2, line3)
let angle1 = Math.atan2(line1, line2) * 180 / Math.PI
let angle2 = Math.atan2(line1, line3) * 180 / Math.PI
let angle3 = 180 - (angle1 + angle2)
console.log(angle1, angle2, angle3)
let arr1 = []
arr1.push(angle1, angle2, angle3)
let line4 = (Math.abs(c[8]) - Math.abs(c[6])) + (Math.abs(c[9]) - Math.abs(c[7]))
let line5 = (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1]))
let line6 = Math.abs(Math.sqrt( Math.pow(line4, 2)+ Math.pow(line5, 2)))
console.log(line4, line5, line6)
let angle4 = Math.atan2(line4, line5) * 180 / Math.PI
let angle5 = Math.atan2(line4, line6) * 180 / Math.PI
let angle6 = 180 - (angle4 + angle5)
console.log(angle6, angle5, angle4)
if (arr1.includes(angle4) && arr1.includes(angle5) && arr1.includes(angle6)){
return result = true
} else return result = false
}
console.log(areTrianglesSimilar(s))
this was my try but did not pass all tests, any better idea?
Thanks to Mbo
function areTrianglesSimilar(c) {
let dx1 = c[2] - c[0];
let dy1 = c[3] - c[1];
let dx2 = c[4] - c[0];
let dy2 = c[5] - c[1];
let dx3 = c[4] - c[2];
let dy3 = c[5] - c[3];
let l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
let l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
let l3 = Math.sqrt(dx3 * dx3 + dy3 * dy3);
console.log(l1,l2,l3);
let angle12 = Math.acos((dx1 * dx2 + dy1 * dy2) / (l1 * l2));
let angle13 = Math.acos((dx1 * dx3 + dy1 * dy3) / (l1 * l3));
let angle23 = Math.acos((dx3 * dx2 + dy3 * dy2) / (l3 * l2));
console.log(angle12, angle13, angle23);
let dx4 = c[8] - c[6];
let dy4 = c[9] - c[7];
let dx5 = c[10] - c[6];
let dy5 = c[11] - c[7];
let dx6 = c[10] - c[8];
let dy6 = c[11] - c[9];
let l4 = Math.sqrt(dx4 * dx4 + dy4 * dy4);
let l5 = Math.sqrt(dx5 * dx5 + dy5 * dy5);
let l6 = Math.sqrt(dx6 * dx6 + dy6 * dy6);
console.log(l4,l5,l6);
let angle45 = Math.acos((dx4 * dx5 + dy4 * dy5) / (l4 * l5));
let angle46 = Math.acos((dx4 * dx6 + dy4 * dy6) / (l4 * l6));
let angle56 = Math.acos((dx6 * dx5 + dy6 * dy5) / (l6 * l5));
console.log(angle45, angle46, angle56);
if (angle12 == angle45 && angle13 == angle46){
console.log('result'+':'+ true);
} else console.log("result" + ":" + false);
}
let coordinates = [3, 4, 4, 7, 6, 1, -2, -1, 0, 5, 4, -7];
console.log(areTrianglesSimilar(coordinates))
Your calculation is completely wrong. Dot product approach:
dx1 = c[2] - c[0]
dy1 = c[3] - c[1]
dx2 = c[4] - c[0]
dy2 = c[5] - c[1]
dx3 = c[4] - c[2]
dy3 = c[5] - c[3]
l1 = Math.sqrt(dx1*dx1+dy1*dy1)
l2 = Math.sqrt(dx2*dx2+dy2*dy2)
l3 = Math.sqrt(dx3*dx3+dy3*dy3)
angle12 = Math.acos((dx1*dx2+dy1*dy2)/(l1*l2)
and similar for angle13, and later you need to compare only two angles for equality
if angle12 == angle45 and angle13 == angle46 ...
or use some epsylon value to avoid floating calculation errors
if abs(angle12 -angle45) < 0.0000001 ...
Moreover, you can avoid angles and compare side length ratios
if l1/l4==l2/l5 and l1/l4==l3/l6...
There are quite a number of errors here. Aside from these, you should probably consider refactoring the code into separate functions which encapsulate commonly performed calculations. This will cut down on needless repetition and make copy-paste typos less possible. It will also make the code a little more self-documenting, which allows human beings to understand what you're doing better.
Assuming you want to determine the angles of the triangles and compare them (but you could also use side length ratios as #MBo pointed out), the general approach I would follow is this:
Write a function to convert the coordinates array into a pair of Triangle objects, where a Triangle is a three-tuple of Point objects, defined like this:
type Triangle = [Point, Point, Point];
interface Point { x: number, y: number };
function toTrianges(coords: number[]): [Triangle, Triangle] {
// implement this
}
Write a function that takes three Points, A, B, and C, and returns the (absolute value of the) measure of angle ∡ABC (with B as the vertex) in, say, degrees:
function measureAngleABC(a: Point, b: Point, c: Point): number {
// implement this
}
In order to do that, you might want to write functions that turn two Points A and B and produces the Vector from A to B, and that manipulate vectors:
type Vector = Point;
function vector(a: Point, b: Point): Vector { /* impl */ }
function vectorLength(v: Vector): number { /* impl */ }
function dotProduct(v1: Vector, v2: Vector): number { /* impl */ }
Note that the (unsigned) angle between two vectors can be determined by examining their lengths and their dot product.
Once you have these, you should be able to turn a Triangle into a (sorted) triplet of its (unsigned) angles:
type TriangleAngles = [number, number, number];
function angles(triangle: Triangle): TriangleAngles { /* impl * }
And finally, write a function that compares two TriangleAngles for near-equality. Not actual equality using ===, which is fraught with troubles. Since floating-point numbers do not have infinite precision, two different calculations that should yield the same quantity might actually produce two different floating-point results. The famous example is that 0.1 + 0.2 === 0.3 is false. When you compare two TriangleAngles, you need to decide how close is "close enough" to call two triangles similar:
function areNearlyEqual(ta1: TriangleAngles, ta2: TriangleAngles): boolean {
// impl here
}
I'm not going to write out how to implement these, since this looks like an exercise that benefits you most if you actually do it, not if someone does it for you.
In any case, here are the errors I see in your code:
The line (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1])) looks like a typo with indices, as you are seemingly comparing a point from one triangle with a point on a different triangle. This sort of typo would be much less likely if you refactor so as to move from an array of numbers to something like a pair of Triangles.
All code of the form Math.abs(c[k]) for some index k is highly suspect. This treats c[k] === 100 identically to c[k] === -100. If you take a triangle and flip the sign of the x or y coordinate of one of its vertices, you are almost certainly going to change the shape of the triangle by reflecting that vertex across the x or y axis:
If your code can't tell the difference between those two triangles, it's not going to be able to accurately determine if two triangles are similar or not.
The line let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1])) and its brethren seem to looking at one of the sides of one of the triangles and adding the x component of its length to the y component of its length to get a single number. This doesn't represent much of anything that I can think of. The vector of x-component-of-length and y-component-of-length are important, but when you just add the components together you are throwing away information you need. You can verify this for yourself by coming up with a triangle where swapping c[2] and c[3] will change its shape, but the above code will not see a difference.
The line let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2))) seems to assume that line1 and line2 represent the lengths of two sides of a right triangle and line3 is the length of the hypotenuse. But unless your two sides are really perpendicular to each other, this will not be true.
The line let angle2 = Math.atan2(line1, line3) * 180 / Math.PI is calculating an angle, but what angle? You can only use the arctangent to get an angle from the opposite and adjacent sides of a right triangle. But there might be no right triangles here, and since line3 was earlier assumed to be the hypotenuse of a right triangle where one of the sides was line1, there's no way line3 is now one of the perpendicular legs.
Um, I think I have to stop here. Suffice it to say that I would be very surprised if you could get this algorithm working by tweaking it. I'd strongly recommend starting over with reusable functions that perform well-defined calculations.
Good luck.
I'm trying to understand how zooming works. I use Vue and the panzoom project.
I found out that I can use the smoothZoom function in order to zoom. But I struggle to understand which parameters it should get.
From chrome I see that I can use the following function of panzoom
ƒ smoothZoom(clientX, clientY, scaleMultiplier) {
var fromValue = transform.scale
var from = {scale: fromValue}
var to = {scale: scaleMultiplier * fromValue}
But I don't understand what is the purpose of clientX, clientY, scaleMultiplier.
For now, I use the function as following:
var transform = this.newArea.getTransform();
var deltaX = transform.x;
var deltaY = transform.y;
var scale = transform.scale;
var newScale = scale + this.minScale; // minScale is set to 0.2
this.newArea.smoothZoom(deltaX,deltaY,newScale);
But for some reason it does not zoom as expected, it could zoom in left, zoom in right, even zoom out.
I create newArea as following:
const area = document.querySelector('.chart');
this.newArea = panzoom(area, { maxZoom: this.maxZoom, minZoom: this.minZoom, zoomSpeed: this.zoomSpeed });
I think that I don't fully understand the meaning of the arguments and probably my algorithm does not work.
How should I change the deltaX, deltaY and newScale so it work (I mean which arguments should I pass)?
Okay maybe i missunderstood the issue:
so the smoothZoom method actually calls amator
this then calls for each timedelta within the growth from the current scale to scale * scaleMultiplier, ({scale}) => zoomAbs(clientX, clientY, scale)
zoomAbs then calculates the ratio and calls zoomRatio wich asigns transform x,y and scale and triggers callbacks and lastly calls applyTransform
clientX, clientY will be calculated by transformToScreen and passed to transform,
but I suggest to read the source to get a deeper insight
scaleMultiplier is the multiplier for the zoom.
Short
deltax does css translate-x
deltay does css translate-y
scale does css scale
Insight
Panzoom uses css transform matrices: applyTransform
and as per readable specs: matrix(a, b, c, d, tx, ty)
where in the used variance:
a is the scalefactor in x direction
b is 0
c is 0
d is the scalefactor in y direction
tx is the translation in x direction
ty is the translation in y direction
for a deeper knowledge one can just calculate some examples:
Given a shape with point A, B, C, D.
Now for any valid transformmatrix M the resulting points A', B', C', D' are calculated this way:
M x A = A' (to not violate math A will be (0,0,1,1) so 4x4 times 4x1)
...
M x D = D'
so in detail for A = (x,y,1,1):
A'.x = A.x * a + A.y * c + 1 * 0 + 1 * tx,
and with c = 0:
A'.x = A.x * a + tx,
analog:
A'.y = A.x * b + A.y * d + 1 * 0 + 1 * ty,
and with b = 0:
A'.y = A.y * d + ty
div {
width: 80px;
height: 80px;
background-color: skyblue;
}
.changed {
transform: matrix(1, 0, 0, 2, 20, 20);
background-color: pink;
}
<div>Normal</div>
<div class="changed">Changed</div>
I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).
If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).
I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:
var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
x: 70,
y: 70
}
var length = 60
var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#00f",
width: 4
})
blueLine.angle = 0
var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#0f0",
width: 4
})
greenLine.angle = 0
var previous = 0
var dt = 0
var step = function(timestamp) {
dt = timestamp - previous
previous = timestamp
blueLine.angle += 0.18 * dt
blueLine.rotate(blueLine.angle, origin.x, origin.y)
var endX = Math.cos(toRad(blueLine.angle)) * length
var endY = Math.sin(toRad(blueLine.angle)) * length
// Comment out this line, and rotation works fine
greenLine.move(endX, endY)
greenLine.angle = blueLine.angle - 10
// Comment out this line, and movement works fine
greenLine.rotate(greenLine.angle, origin.x, origin.y)
// But they don't work together. If I both move and rotate
// the green line, it goes in this crazy huge arc, rather
// than rotating neatly around the end of the blue line
// as expected.
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
function toRad(deg) {
return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>
Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.
I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.
Thank you kind vector graphics experts!
Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:
Rider.Leg = function(foot, front, xOffset, yOffset) {
var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
this.foot = foot
this.draw = foot.draw
this.geo = {
upper: {
x: this.foot.pedal.gear.x + 150,
y: this.foot.pedal.gear.y - 750,
length: 396
},
lower: {
length: 390
}
}
this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
.transform({ scale: 0.95, cx: 0, cy: 0 })
this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}
// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
var angle = this.pedalAngle() - Math.PI
var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
var x = this.geo.upper.length * Math.cos(ha + angle)
var y = this.geo.upper.length * Math.sin(ha + angle)
this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)
}
// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.hypot(xDist, yDist)
}
// Gets the angle between the hip joint and the pedal
Rider.Leg.prototype.pedalAngle = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.atan2(yDist, xDist)
}
Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}
When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.
Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:
greenLine.transform({x:endX, y:endY, relative: true})
To move the line by a certain amount. That should work way better.
Take a typical cubic bezier curve drawn in JavaScript (this example I googled...)
http://jsfiddle.net/atsanche/K38kM/
Specifically, these two lines:
context.moveTo(188, 130);
context.bezierCurveTo(170, 10, 350, 10, 388, 170);
We have a cubic bezier which starts at 188, 130, ends at 388, 170, and has controls points a:170, 10 and b:350, 10
My question is would it be possible to mathematically adjust the end point and control points to make another curve which is only a segment of the original curve?
The ideal result would be able to able to take a percentage slice of the bezier from the beginning, where 0.5 would draw only half of the bezier, 0.75 would draw most of the bezier (and so on)
I've already gotten working a few implementations of De Castelau which allow me to trace the contour of the bezier between [0...1], but this doesn't provide a way to mathematically recalculate the end and control points of the bezier to make a sub-bezier...
Thanks in advance
De Casteljau is indeed the algorithm to go. For a cubic Bezier curve defined by 4 control points P0, P1, P2 and P3, the control points of the sub-Bezier curve (0, u) are P0, Q0, R0 and S0 and the control points of the sub-Bezier curve (u, 1) are S0, R1, Q2 and P3, where
Q0 = (1-u)*P0 + u*P1
Q1 = (1-u)*P1 + u*P2
Q2 = (1-u)*P2 + u*P3
R0 = (1-u)*Q0 + u*Q1
R1 = (1-u)*Q1 + u*Q2
S0 = (1-u)*R0 + u*R1
Please note that if you want to "extract" a segment (u1, u2) from the original Bezier curve, you will have to apply De Casteljau twice. The first time will split the input Bezier curve C(t) into C1(t) and C2(t) at parameter u1 and the 2nd time you will have to split the curve C2(t) at an adjusted parameter u2* = (u2-u1)/(1-u1).
This is how to do it. You can get the left half or right half with this functin. This function is take thanks to mark from here: https://stackoverflow.com/a/23452618/1828637
I have it modified so it can be fit to a unit cell so we can use it for cubic-bezier in css transitions.
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
Thats the function and now to use it with your parameters.
var myBezier = {
xs: [188, 170, 350, 388],
ys: [130, 10, 10, 170]
};
var splitRes = splitCubicBezier({
z: .5, //percent
x: myBezier.xs,
y: myBezier.ys,
fitUnitSquare: false
});
This gives you
({
left: [188, 130, 179, 70, 219.5, 40, 267, 45],
right: [267, 45, 314.5, 50, 369, 90, 388, 170]
})
fiddle proving its half, i overlaid it over your original:
http://jsfiddle.net/K38kM/8/
Yes it is! Have a look at the bezier section here
http://en.m.wikipedia.org/wiki/De_Casteljau's_algorithm
It is not that difficult all in all.
I need a function that creates a 2D array resembling an ellipse where each cell is a pixel that can either be on (1) or off (0). For example, if you ran circlearray(5,8), it would return something like:
[[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0]];
I have tried this before, but can't seem to get the rounding (decimal to integer, not making it elliptical) right. The formula that I'm using is: f(x) = h/w * sqrt(w^2 - x^2), where h is the length of the radius at the top of the ellipse and w is the length of the radius at the side of the ellipse. It gives you how many columns you should have for the circle given row, but with circles, I notice that it is different turned on its side that upright, which I know shouldn't be. I can't seem to get the circle right with Math.round(f(x)), Math.floor(f(x)), nor Math.ceil(f(x)). I also don't want to use jQuery.
Here is a the jsFiddle shows my findings: http://jsfiddle.net/r7cH5/
And here is an example of how to use the formula: http://www.desmos.com/calculator/v5qbcd1jkm
I assume you mean an ellipse with major and minor axes aligned horizontally and vertically. (Plotting an ellipse in general orientation is a much more difficult problem.)
You want a generalization of the Bresenham circle algorithm for ellipses. A nice description of the theory and an implementation is described in this paper. Simple code can be found here for using Bresenham's algorithm for lines, circles, ellipses and Bézier curves. From the latter site, here's the code for plotting an ellipse inside a specified rectangle:
void plotEllipseRect(int x0, int y0, int x1, int y1)
{
int a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; /* values of diameter */
long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */
long err = dx+dy+b1*a*a, e2; /* error of 1.step */
if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
if (y0 > y1) y0 = y1; /* .. exchange them */
y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */
a *= 8*a; b1 = 8*b*b;
do {
setPixel(x1, y0); /* I. Quadrant */
setPixel(x0, y0); /* II. Quadrant */
setPixel(x0, y1); /* III. Quadrant */
setPixel(x1, y1); /* IV. Quadrant */
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */
} while (x0 <= x1);
while (y0-y1 < b) { /* too early stop of flat ellipses a=1 */
setPixel(x0-1, y0); /* -> finish tip of ellipse */
setPixel(x1+1, y0++);
setPixel(x0-1, y1);
setPixel(x1+1, y1--);
}
}
This isn't JavaScript, of course, but it should be straightforward to convert. (Since no integer division is involved, you don't need to worry about the fact that JavaScript doesn't have integer division.) Instead of setPixel, of course, you would simply set the array element to 1.
Note that this plots a one-pixel thick ellipse boundary. If you want a filled ellipse, just apply a scan line fill: iterate row by row and fill between the two columns that are set to 1 by the above (or leave the row alone if you don't find two distinct columns set). Alternatively, you could follow up the above with a flood fill algorithm starting at the center of the rectangle.
var width = 100,
height = 160,
span = document.createElement('span'),
output = document.getElementById("output");
function ellipse(x) {
return Math.floor((height / width) * Math.sqrt((width * width) - (x * x))); // Times itself doesn't give NaN as often as squared.
}
for (var i = -1 * width; i <= width; i++) {
var s = span.cloneNode(false);
s.style.height = ellipse(i) + 'px';
output.appendChild(s)
}
Demo