Hope I'm posting this in the right section, apologys if not.
I have a expression that I use on an objects position in After Effects:
radius = 35;
startAngle = degreesToRadians(180);
T = 22;
angularSpeed = 2 * 3.1415926 / T;
xt =1082 + radius * Math.cos(angularSpeed * time + startAngle);
yt = 536 + radius * Math.sin(angularSpeed * time + startAngle);
[xt, yt]
My questions, is how do I make it go counter clockwise as oppsoed to cloackwise?
Any help appreciated,
Ian
Related
I'm trying to draw an SVG path in a PDF using itextsharp v5.
The approach I am following is roughly this:
Reading the SVG path from the SVG file (Svg.SvgPath)
Getting the list of segments from the path ({Svg.Pathing.SvgPathSegmentList})
Creating an iTextSharp PdfAnnotation and associate a PdfAppearance to it
Drawing each segment in the SvgPathSegmentList using the corresponding PdfContentByte method ( for SvgLineSegment I use PdfContentByte.LineTo, for SvgCubicCurveSegment I use PdfContentByte.CurveTo )
For most of the SvgPathSegments types, there is a clear mapping between values in the SvgPathSegments and the arguments in the PdfContentByte method. A few examples:
SvgMoveToSegment has the attribute End which is the target point (X, Y) and the PdfContentByte.MoveTo takes two parameters: X, Y
SvgLineSegment, very similar to the Move. It has the Target End and the PdfContentByte.LineTo takes two parameters X and Y and draws a line from the current position to the target point.
app.MoveTo(segment.Start.X, segment.Start.Y);
SvgCubicCurveSegment has all you need to create a Bezier curve (The Start point, the End point, and the first and second control point). With this I use PdfContentByte.CurveTo and get a curve in the PDF that looks exactly as it looks in the SVG editor.
var cubicCurve = (Svg.Pathing.SvgCubicCurveSegment)segment;
app.CurveTo(
cubicCurve.FirstControlPoint.X, cubicCurve.FirstControlPoint.Y,
cubicCurve.SecondControlPoint.X, cubicCurve.SecondControlPoint.Y,
cubicCurve.End.X, cubicCurve.End.Y);
The problem I have is with the ARC ("A" command in the SVG, SvgArcSegment)
The SvgArcSegment has the following values:
Angle
Start (X, Y)
End (X, Y)
RadiusX
RadiusY
Start
Sweep
On the other hand, PdfContentByte.Arc method expect:
X1, X2, Y1, Y2
StartAngle,
Extent
As per the itextsharp documentation, Arc draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2 starting (counter-clockwise) at StartAngle degrees and covering extent degrees. I.e. startAng=0 and extent=180 yield an openside-down semi-circle inscribed in the rectangle.
My question is: How to "map" the values in the SvgArcSegment created from the SVG A command into the arguments that PdfContentByte.Arc method expects.
I know that the Start and End values are indeed the origin and target of the curve I want, but no clue what RadiusX and RadiusY mean.
As #RobertLongson pointed in his comment, what I needed was to convert from Center to Endpoint Parametrization.
I'm posting my own C# implementation of the algorithm documented in the SVG documentation, just in case someone else needs it.
public static SvgCenterParameters EndPointToCenterParametrization(Svg.Pathing.SvgArcSegment arc)
{
//// Conversion from endpoint to center parameterization as in SVG Implementation Notes:
//// https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter
var sinA = Math.Sin(arc.Angle);
var cosA = Math.Cos(arc.Angle);
//// Large arc flag
var fA = arc.Size == Svg.Pathing.SvgArcSize.Large ? 1 : 0;
//// Sweep flag
var fS = arc.Sweep == Svg.Pathing.SvgArcSweep.Positive ? 1 : 0;
var radiusX = arc.RadiusX;
var radiusY = arc.RadiusY;
var x1 = arc.Start.X;
var y1 = arc.Start.Y;
var x2 = arc.End.X;
var y2 = arc.End.Y;
/*
*
* Step 1: Compute (x1′, y1′)
*
*/
//// Median between Start and End
var midPointX = (x1 - x2) / 2;
var midPointY = (y1 - y2) / 2;
var x1p = (cosA * midPointX) + (sinA * midPointY);
var y1p = (cosA * midPointY) - (sinA * midPointX);
/*
*
* Step 2: Compute (cx′, cy′)
*
*/
var rxry_2 = Math.Pow(radiusX, 2) * Math.Pow(radiusY, 2);
var rxy1p_2 = Math.Pow(radiusX, 2) * Math.Pow(y1p, 2);
var ryx1p_2 = Math.Pow(radiusY, 2) * Math.Pow(x1p, 2);
var sqrt = Math.Sqrt(Math.Abs(rxry_2 - rxy1p_2 - ryx1p_2) / (rxy1p_2 + ryx1p_2));
if (fA == fS)
{
sqrt = -sqrt;
}
var cXP = sqrt * (radiusX * y1p / radiusY);
var cYP = sqrt * -(radiusY * x1p / radiusX);
/*
*
* Step 3: Compute (cx, cy) from (cx′, cy′)
*
*/
var cX = (cosA * cXP) - (sinA * cYP) + ((x1 + x2) / 2);
var cY = (sinA * cXP) + (cosA * cYP) + ((y1 + y2) / 2);
/*
*
* Step 4: Compute θ1 and Δθ
*
*/
var x1pcxp_rx = (float)(x1p - cXP) / radiusX;
var y1pcyp_ry = (float)(y1p - cYP) / radiusY;
Vector2 vector1 = new Vector2(1f, 0f);
Vector2 vector2 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var angle = Math.Acos(((vector1.x * vector2.x) + (vector1.y * vector2.y)) / (Math.Sqrt((vector1.x * vector1.x) + (vector1.y * vector1.y)) * Math.Sqrt((vector2.x * vector2.x) + (vector2.y * vector2.y)))) * (180 / Math.PI);
if (((vector1.x * vector2.y) - (vector1.y * vector2.x)) < 0)
{
angle = angle * -1;
}
var vector3 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var vector4 = new Vector2((float)(-x1p - cXP) / radiusX, (float)(-y1p - cYP) / radiusY);
var extent = (Math.Acos(((vector3.x * vector4.x) + (vector3.y * vector4.y)) / Math.Sqrt((vector3.x * vector3.x) + (vector3.y * vector3.y)) * Math.Sqrt((vector4.x * vector4.x) + (vector4.y * vector4.y))) * (180 / Math.PI)) % 360;
if (((vector3.x * vector4.y) - (vector3.y * vector4.x)) < 0)
{
extent = extent * -1;
}
if (fS == 1 && extent < 0)
{
extent = extent + 360;
}
if (fS == 0 && extent > 0)
{
extent = extent - 360;
}
var rectLL_X = cX - radiusX;
var rectLL_Y = cY - radiusY;
var rectUR_X = cX + radiusX;
var rectUR_Y = cY + radiusY;
return new SvgCenterParameters
{
LlX = (float)rectLL_X,
LlY = (float)rectLL_Y,
UrX = (float)rectUR_X,
UrY = (float)rectUR_Y,
Angle = (float)angle,
Extent = (float)extent
};
}
the drawing algorithm which I currently use:
a_max = Pi*2 (float)(num_segments - 1.0f)/(float)num_segments;
for (unsigned int i = 0; i<=num_segments;i++)
{
const float a = (float)i / (float)num_segments * a_max;
SetPixel(centre.x + cos(a) *radius, centre.y +sin(a) *radius);
}
Works fine, but it starts drawing at (centre.x+radius, centre.y). I would like to have it to start at the top , because I want to draw a compass and zero degree is at the top, not on the right, so that I don't have to make a hacky solution.
Try rotating 90 degrees to the left before you start drawing, this should solve it for you.
A compass not only starts at "north" instead of "east" but also goes clockwise instead of counter-clockwise.
For this case, just swap sin(a) and cos(a):
x = centre.x + sin(a) * radius
y = centre.y + cos(a) * radius
I'm not sure what to search for or how to ask the question as I can't draw. Please bear with me.
If I have a rectangle with circular end caps. I want to remove some of the edges of the rectangle so there is a smooth path all round. Kinda like of you were to stretch the ends, the middle gets thinner.
I was trying to work out the chord of a larger, outer circle until I got stuck trying to work out where the circles should touch.
I can see some relationships for trigonometry, but my brain just won't go the extra mile.
Can anyone please help point me in the right direction.
Thanks.
Here is the answer:
// Small value for CSG
Delta = 0.01;
2Delta = 2 * Delta;
$fa=1; $fs=$fa;
module roudedArm(xl=50, yt=10, zh=5, in=2, bh=0.8) {
EndRadius = yt/2; // size of outer ends
EndSpacing = xl-yt; // distance between end point radii
ArmConcavity = in; // how much in does it go in on each side
ArmThickness = zh; // height in z
// Negative curve to narrow the Arm (calculated by pythagoras)
ArmCurveRadius = (pow((EndSpacing / 2), 2) - 2 * EndRadius * ArmConcavity + pow(ArmConcavity, 2)) / (2 * ArmConcavity);
// The orthogonal distance between the middle of the Arm the point it touches the round pillar sections
ArmSectionLength = (EndSpacing / 2) * ArmCurveRadius / (ArmCurveRadius + EndRadius);
// end points
lbxcylinder(r=EndRadius, h=ArmThickness);
translate([EndSpacing, 0, 0]) lbxcylinder(r=EndRadius, h=ArmThickness);
// inner curve
difference()
{
translate([EndSpacing / 2 - ArmSectionLength, -EndRadius -ArmThickness, 0])
translate([ArmSectionLength, (EndRadius + ArmThickness),0])
lbxcube([ArmSectionLength * 2, 2 * (EndRadius + ArmThickness), ArmThickness], bh=bh);
// Cut out Arm curve
translate([EndSpacing / 2, ArmCurveRadius + EndRadius - ArmConcavity, -Delta])
lbxcylinder(r = ArmCurveRadius, h = ArmThickness + 2Delta, bh=-bh);
translate([EndSpacing / 2, -(ArmCurveRadius + EndRadius - ArmConcavity), -Delta])
lbxcylinder(r = ArmCurveRadius, h = ArmThickness + 2Delta, bh=-bh);
}
}
module lbxcube(size, bh=0.8) {
// don't support bevelling in demo
translate([-size[0]/2, -size[1]/2, 0]) cube(size);
}
module lbxcylinder(r, h, bh=0.8) {
// don't support bevelling in demo
cylinder(r=r, h=h);
}
roudedArm(xl=50, yt=10, zh=5, in=2, bh=0.8);
Thanks to Rupert and his Curvy Door Handle on Thingiverse.
I am struggling with the following problem:
I am given n points and a radius and I have to place them on a circle as symmetrical as possible.
Currently, I used something like this:
float theta = 360.0f / n;
int i = 0;
for (Word w : e.getValue()) {
double newX = Math.sin(theta * i) * RADIUS + I_OFFSET_X;
double newY = Math.cos(theta * i) * RADIUS + I_OFFSET_Y;
mxCell v2 = (mxCell) graph.insertVertex(parent, null, w.getValue(), newX, newY, OW_WIDTH, OW_HEIGHT,"shape=ellipse");
graph.insertEdge(parent, null, "", v1, v2);
i++;
}
where n is my number of points.
This works fine for a large enough n, but for n=3 for example, I get something like:
I would actually like to have something like:
(bad drawing skills are bad..)
So basically, something as symmetric as possible would be awesome.
Any hints on how to solve this?
Thanks <3
Thanks to Jongware, the answer was quite obvious. Because I'm dealing with Java, all the sin/cos parameters should be in radians.
Fix:
double newX = Math.sin(Math.toRadians(theta * i)) * RADIUS + I_OFFSET_X;
double newY = Math.cos(Math.toRadians(theta * i)) * RADIUS + I_OFFSET_Y;
Works like a charm
Given a line made up of several points, how do I make the line smoother/ curvier/ softer through adding intermediate points -- while keeping the original points completely intact and unmoved?
To illustrate, I want to go from the above to the below in this illustration:
Note how in the above picture, if we start at the bottom there will be a sharper right turn. In the bottom image however, this sharp right turn is made a bit "softer" by adding an intermediate point which is positioned in the middle of the two points, and using averages of the angles of the other lines. (Differently put, imagine the lines a race car would drive, as it couldn't abruptly change direction.) Note how, however, none of the original points was "touched", I just added more points.
Thanks!! For what it's worth, I'm implementing this using JavaScript and Canvas.
with each edge (e1 & e2) adjacent to each 'middle' edge (me) do
let X = 0.5 x length(me)
find 2 cubic bezier control points by extending the adjacent edges by X (see algorithm below)
get midpoint of cubic bezier (by applying formula below)
insert new 'midpoint' between me's two coordinates.
FloatPoint ExtendLine(const FloatPoint A, const FloatPoint B, single distance)
{
FloatPoint newB;
float lenAB = sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
newB.X = B.x - (B.x - A.x) / lenAB * distance;
newB.Y = B.Y - (B.Y - A.Y) / lenAB * distance;
return newB;
}
Edit: Formula for Bezier Curve midpoint: p(0.5) = 0.125(p0) + 0.375(p1) + 0.375(p2) + 0.125(p3)
The following code found elsewhere here does the job for me, in the specific context of JavaScript-Canvas which I'm using -- but please see Angus' answer for a more general approach:
var max = points.length;
context.beginPath();
var i = 0;
context.moveTo(points[i].x, points[i].y);
for (i = 1; i < max - 2; i++) {
var xc = (points[i].x + points[i + 1].x) * .5;
var yc = (points[i].y + points[i + 1].y) * .5;
context.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
}
context.quadraticCurveTo(points[max - 2].x, points[max - 2].y, points[max - 1].x,points[max - 1].y);
context.closePath();
context.stroke();