Polyline following a curved path - wpf

I code a diagram editor and I draw some curved links that perfectly works from quadratic bezier segment (see picture) :
I search the best way (and if it's possible) to draw "spike" curved link. Approximatively like this (in blue) :
I have no idea where to start, I read few articles on drawingbrush or "how to draw a curved text" but it doesn't seems to be what I need...
Thanks for your help or advises ! :)
Just to complete few remarks, the bezier is made with quadrametricBezier class from 3 points. Thanks to all of you !

I don't think there's a built-in way in WPF to do that. You'll have to calculate the coordinates yourself and draw the lines yourself (e.g. using DrawingVisual).
To calculate the coordinates, you would have to:
Step 1 Sample points along the bezier curve.
A bezier curve with 4 control points has the formula:
curve(t) = t^3 p1 + 3 t^2 (1-t) p2 + 3 t (1-t)^2 p3 + (1-t)^3 p4
d/dt curve(t) = 3 p3 - 3 p4 + 6 p2 t - 12 p3 t + 6 p4 t + 3 p1 t^2 - 9 p2 t^2 + 9 p3 t^2 - 3 p4 t^2
With these formulas, you can calculate points on the curve, and their tangent directions. Rotating the tangent direction by 90° (i.e. swap X/Y and change the sign of Y) gives the normal direction.
However, these points are not equidistant:
So if you used these points directly, you'd get a curve where some of the "spikes" are shorter than others:
Step 2: Get equidistant points along the curve
You now have a list of points along the curve. You can calculate the euclidean distance between each point and the next. Summing all those distances up gives the total length of the curve.
Let's say you want spikes that are (roughly) 10 pixels wide. Then you need n=round(TotalLength / 10) points. The points are at s(i) = TotalLength / n * i.
So if you want to e.g. find the value of t for the 3rd equidistant point, you'd calculate s(3) = TotalLength / n * 3. Then you'd iterate over the set of sampled points, summing up the distances as you go, until you reach a point that has a total distance along the curve > s(3). Now you know the points immediately before and after the point you're looking for, and you can use the rule of three to calculate the t in between.
Now you have a set of points that are the same distance apart along the curve:
Step 3: Drawing the spikes
This is the easiest part: At each of your equidistant points, calculate the normal (using the derivative formula above). Divide that normal by its length to get the unit normal. Then add to each even point +d * UnitNormal and to each odd point -d * UnitNormal, where d is the "depth" of the spike, i.e. the distance of the tip to the curve.

Assuming that you already have computed Bezier curve, desired curve is a sum of triangle wave multiplied to normal vector to the Bezier curve with the Bezier curve. The only thing you should take into account that the Bezier curve is a parametric curve with parameter t in [0, 1]. Then you need Bezier curve length function L(t) and to plug that into triangle wave equation instead of t.
Triangle wave also can be expressed through modulo operation
TW(t) = M * abs(mod(q * L(t), n * 2 - 2) - n + 1) + 1
where
M - a magnitude of the wave,
q - a scaling factor along path,
n - period of a curve,
t - parameter of a Bezier curve,
L(t) - length function of Bezier curve.
Resulting curve:
C(t) = TW(t) * B_normal(t) + B(t)
where B_normal(t) is a normal vector to a Bezier curve at point t.

For those that can be interested by the WPF solution, I finally code this (not very optimized) based on the QuadraticBezierSegment and PathGeometry classes.
Thanks very much to all of you. :)
public partial class MainWindow : Window
{
int orientation = 1;
int compt = 0;
int SpikeWidth = 5;
int SpikeHeigth = 3;
public MainWindow()
{
InitializeComponent();
Polyline wave = new Polyline();
wave.Stroke = Brushes.Blue;
wave.StrokeThickness = 2;
PathGeometry pg = BezierPath.Data.GetFlattenedPathGeometry();
double CurveLenght = GetLength(pg, PathFigure.StartPoint);
double NbrPoint = (Math.Round(CurveLenght / SpikeWidth));
for (int i = 0; i <= NbrPoint; i++)
{
//Calcul de T
double t = SpikeWidth * i / CurveLenght;
Point TangentPoint;
Point PointToDraw;
pg.GetPointAtFractionLength(t, out PointToDraw, out TangentPoint);
// Calcul de l'angle
double a = Math.Atan2(TangentPoint.Y, TangentPoint.X);
a += Math.PI / 2;
//Alterner un point sur deux de chaque coté de la courbe
if (compt % 2 == 0)
orientation = 1;
else
orientation = -1;
//Calcul du point et ajout à la polyligne.
//Point calculation and added to the polyline.
wave.Points.Add(new Point(Math.Cos(a) * SpikeHeigth * orientation + PointToDraw.X, Math.Sin(a) * SpikeHeigth * orientation + PointToDraw.Y));
//Compte le nombre de passage pour l'orientation
compt += 1;
}
//Traçage sur la canvas
cv.Children.Add(wave);
}
private double GetLength(PathGeometry pg, Point startPoint)
{
PolyLineSegment pls = pg.Figures[0].Segments[0] as PolyLineSegment;
double distance = 0;
foreach (Point pt in pls.Points)
{
distance += Math.Sqrt((startPoint.X - pt.X).Pow(2) + (startPoint.Y - pt.Y).Pow(2));
startPoint = pt;
}
return distance;
}
}
<Canvas x:Name="cv">
<Path Stroke="Black" x:Name="BezierPath">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="PathFigure" StartPoint="10,400">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment x:Name="BezierSegment" Point1="50,80" Point2="400,400">
</QuadraticBezierSegment>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>

Related

Floor casting algorithm just doing weird things

I'm actually using the algorithme of Toto Briac from Raycaster, searching for more effective floor/ceiling raycast
When using it, the floor casting is working well for east and west side, but for north and south it just does weird things (just look image).
double pixelsToBottom;
double pixelsToMid;
double directDistFloor;
double realDistance;
double y;
t_point f_p;
pixelsToBottom = (double)data->s_height - wall[1].y;
pixelsToMid = (double)data->s_height / 2 - pixelsToBottom;
for (int i = pixelsToMid; i < data->s_height / 2; i += 1)
{
directDistFloor = (data->dist_proj * (double)(data->s_height / 2)) / i;
realDistance = directDistFloor / fabs(cos(angle));
f_p.x = data->player.pos.x + cos(angle) * (realDistance) / (data->dist_proj / (64.0));
f_p.y = data->player.pos.y + sin(angle) * (realDistance) / (data->dist_proj / (64.0));
y = (wall->x + (i + data->s_height / 2) * data->s_width) / data->s_width;
pixel_put(&data->obj, wall->x, y, f_pixel(data, f_p));
}
But i'm facing an issu, when i'm facing north and south side it's all ok, but when the ray is going into east or west side, the texture just do a weird thing like that :
I know that it refer to : realDistance = directDistFloor / fabs(cos(angle));
if i replace the cos(angle) by sin(angle) in this line, it just invert things. I didn't find a way to change the calcule in right moment. I you have any idea I will take it ! Thank you !!
It's okay I found the answer, for the people who are interest, in the line realDistance = directDistFloor / fabs(cos(angle)); I was using the angle of the ray in the world, I changed it by the angle relative to my player dir (so 0° if it's the player dir ray) and it work properly ! Thank's #ZwergofPhoenix for the time you took !

How to calculate the sizes of a rectangle that contains rotated image (potentially with transparent pixels)

Given theta angles in radians, width and height of the rotated image, how do I calculate the new width and height of the outer rectangle that contains the rotated image?
In other words how do I calculate the new bonding box width/height?
Note that the image could actually be circle and have transparent pixels on the edges.
That would be: x1, y1.
I am actually rotating a pixbuf with the origin at center using cairo_rotate() and I need to know the newly allocated area. What I tried is this:
double geo_rotated_rectangle_get_width (double a, double b, double theta)
{
return abs(a*cos(theta)) + abs(b*sin(theta));
}
And it will work in the sense of always returning sufficient space to contain the rotated image, but it also always returns higher values than it should, when image is not rotated in a multiple of 90o and is a fully opaque image (a square).
EDIT:
This is the image I am rotating:
Interestingly enough, I just tried with a fully opaque image with the same size and it was OK. I use gdk_pixbuf_get_width() to get width and it returns the same value for both regardless. So I assume the formula is correct and the problem is that the transparency is not accounted for. When rotated with a diagonal orientation there are edges from the rectangle of the rotated image that are transparent.
I'll leave the above so that it is helpful to others :)
Now the question becomes how to account for transparent pixels on the edges
To determine the bbox of the rotated rectangle, you can compute the coordinates of the 4 vertices and take the bbox of these 4 points.
a is the width of the unrotated rectangle and b its height ;
let diag = sqrt(a * a + b * b) / 2 the distance from the center to the top right corner of this rectangle. You can use diag = hypot(a, b) / 2 for better precision ;
first compute the angle theta0 of the first diagonal for theta=0: theta0 = atan(b / a) or better theta0 = atan2(b, a) ;
the 4 vertices are:
{ diag * cos(theta0 + theta), diag * sin(theta0 + theta) }
{ diag * cos(pi - theta0 + theta), diag * sin(pi - theta0 + theta) }
{ diag * cos(pi + theta0 + theta), diag * sin(pi + theta0 + theta) }
{ diag * cos(-theta0 + theta), diag * sin(-theta0 + theta) }
which can be simplified as:
{ diag * cos(theta + theta0), diag * sin(theta + theta0) }
{ -diag * cos(theta - theta0), -diag * sin(theta - theta0) }
{ -diag * cos(theta + theta0), -diag * sin(theta + theta0) }
{ diag * cos(theta - theta0), diag * sin(theta - theta0) }
which gives x1 and y1:
x1 = diag * fmax(fabs(cos(theta + theta0)), fabs(cos(theta - theta0))
y1 = diag * fmax(fabs(sin(theta + theta0)), fabs(sin(theta - theta0))
and the width and height of the rotated rectangle follow:
width = 2 * diag * fmax(fabs(cos(theta + theta0)), fabs(cos(theta - theta0))
height = 2 * diag * fmax(fabs(sin(theta + theta0)), fabs(sin(theta - theta0))
This is the geometric solution, but you must take into account the rounding performed by the graphics primitive, so it is much preferable to use the graphics API and retrieve the pixbuf dimensions with gdk_pixbuf_get_width() and gdk_pixbuf_get_height(), which will allow for precise placement.
I'd say "let cairo compute those coordinates". If you have access to a cairo_t*, you can do something like the following (untested!):
double x1, y1, x2, y2;
cairo_save(cr);
cairo_rotate(cr, theta); // You can also do cairo_translate() and whatever your heart desires
cairo_new_path(cr);
cairo_rectangle(cr, x, y, width, height);
cairo_restore(cr); // Note: This preserved the path!
cairo_fill_extents(cr, &x1, &y1, &x2, &y2);
cairo_new_path(cr); // Clean up after ourselves
printf("Rectangle is inside of (%g,%g) to (%g,%g) (size %g,%g)\n",
x1, y1, x2, y2, x2 - x1, y2 - y1);
The above code applies some transformation, then constructs a path. This makes cairo apply the transformation to the given coordinates. Afterwards, the transformation is "thrown away" with cairo_restore(). Next, we ask cairo for the area covered by the current path, which it provides in the current coordinate system, i.e. without the transformation.

Creating a projectile trajectory in Allegro

I'm developing a 2D game in C using Allegro 5, where an enemy from a fixed position shoots a projectile at the player's current position. I know I will have to calculate the tangent of an imaginary triangle based on the player's position and the enemy's. However, how can I have the projectile follow a straight line based on that value?
This is a situation where can be easier to work with vectors than angles.
Some simple math computes the vector between the enemy and the player:
# Compute the x and y displacement from the enemy to the player
dx = player_x - enemy_x
dy = player_y - enemy_y
# normalize the displacement to get the direction vector
distance = sqrt(dx * dx + dy * dy)
projectile.dir_x = dx / distance
```
The projectile just needs to follow that vector during the update loop:
projectile.x += projectile.dir_x * projectile.speed * time_elapsed
projectile.y += projectile.dir_y * projectile.speed * time_elapsed

Moving sprites in sine wave

can someone slap me an idea or math formula on how to make my enemies move in a sine wave
tried something like this but they just move at the same time so they just create a straight line of enemies moving left and right.
for(int i = 0; i < 5; i++){
float y = sinf( 100+delta_time*0.06f) * 75;
float x = game->enemy[i].base_x + y;
game->enemy[i].x = x ;
game->enemy[i].y += 1;
SDL_Rect rect = { game->enemy[i].x , game->enemy[i].y ,game->enemy[i].w, game->enemy[i].h};
SDL_RenderCopy(game->renderer , game->enemy[i].sprite , NULL , &rect);
}
Let v=(v_x,v_y) be the overall direction of the enemy. Let o be the vector o=(-v_y/||v||,v_x/||v||) where ||v||=sqrt(v_x.v_x+v_y.v_y) is the norm of v. The vector o is perpendicular to v. A sinusoidal motion is wanted in that direction. Consequently, the position p(t)=(x(t),y(t)) is defined as :
x(t)=v_x.t-A.v_y/||v||.sin(w.t)
y(t)=v_y.t+A.v_x/||v||.sin(w.t)
where A is the magnitude of the ossilations and w the pulsation of the ossilations. The corresponding frequency is f=w/(2pi).Then, the wavelength lambda=||v||/f corresponds to the length of ossilations.
If the enemy is moving in the x direction (v_y=0) then :
x(t)=v_x.t
y(t)=A.sin(w.t)
The length of ossilations is lambda=2pi.v_x/w.

Algorithm for triangulation of point travelling around a circle

Given the following system:
Where:
A: Point that exists anywhere on the edge of a circle with radius r on the xz plane.
θ: The angle between the positive-x-axis and a vector from the origin to point A. This should range from -PI/2 to PI/2.
B1: A point at the intersection of the circle and the positive x-axis at a height of h1.
B2: A point at the intersection of the circle and the positive z-axis at a height of h2.
d1: Distance between B1 and A.
d2: Distance between B2 and A.
Assuming:
h1, h2, and r are known constants.
d1 and d2 are known variables.
How do I find θ?
This will eventually be implemented in C in an embedded system where I have reasonably fast functions for arctan2, sine, and cosine. As such, performance is definitely a priority, and estimations can be used if they are correct to about 3 decimal places (which is how accurate my trig functions are).
However, even given a mathematical algorithm, I'm sure I could work out the specific implementation.
For what it's worth, I got about as far as:
(d1^2 - h1^2) / r = (sin(θ))^2 + (cos(θ))^2
(d2^2 - h2^2) / r = (sin(PI/4 - θ))^2 + (cos(PI/4 - θ))^2
Before I realized that, mathematically, this is way out of my league.
This isn't a full answer but a start of one.
There are two easy simplifications you can make.
Let H1 and H2 be the points in your plane below B1 and B2.
Since you know h1 and d1, h2 and d2, you can calculate the 2 distances A-H1 and A-H2 (with Pythagoras).
Now you have reduced the puzzle to a plane.
Furthermore, you don't really need to look at both H1 and H2. Given the distance A-H1, there are only 2 possible locations for A, which are mirrored in the x-axis. Then you can find which of the two it is by seeing if the A-H2 distance is above or below the threshold distance H2-H1.
That seems to be a good beginning :-)
Employing #Rhialto, additional simplifications and tests for corners cases:
// c1 is the signed chord distance A to (B1 projected to the xz plane)
// c1*c1 + h1*h1 = d1*d1
// c1 = +/- sqrt(d1*d1 - h1*h1) (choose sign later)
// c1 = Cord(r, theta) = fabs(r*2*sin(theta/2))
// theta = asin(c1/(r*2))*2
//
// theta is < 0 when d2 > sqrt(h2*h2 + sqrt(2)*r*sqrt(2)*r)
// theta is < 0 when d2 > sqrt(h2*h2 + 2*r*r)
// theta is < 0 when d2*d2 > h2*h2 + 2*r*r
#define h1 (0.1)
#define h2 (0.25)
#define r (1.333)
#define h1Squared (h1*h1)
#define rSquared (r*r)
#define rSquaredTimes2 (r*r*2)
#define rTimes2 (r*2)
#define d2Big (h2*h2 + 2*r*r)
// Various steps to avoid issues with d1 < 0, d2 < 0, d1 ~= h1 and theta near pi
double zashu(double d1, double d2) {
double c1Squared = d1*d1 - h1Squared;
if (c1Squared < 0.0)
c1Squared = 0.0; // _May_ be needed when in select times |d1| ~= |h1|
double a = sqrt(c1Squared) / rTimes2;
double theta = (a <= 1.0) ? asin(a)*2.0 : asin(1.0)*2.0; // Possible a is _just_ greater than 1.0
if (d2*d2 > d2Big) // this could be done with fabs(d2) > pre_computed_sqrt(d2Big)
theta = -theta;
return theta;
}

Resources