Smoothly Connecting Circle Centers

Smoothly connecting circle centers


  1. Clarification

    tube has the same circular diameter everywhere so no distortion due to bending is present !!! input is 2 endpoints points (centers of tube) P0,P1 and 2 vectors (normal/direction of tube) N0,N1

    pipe bend

  2. Solution

    Use Interpolation cubic for example this one

    p(t)=a0+a1*t+a2*t*t+a3*t*t*t
    t=<0,1.0>

    so write equations for the known data, solve a0,a1,a2,a3 coefficients for each axis you need (2D: x,y) and then you can get the center point and its normal in any point along the bend side which is what you need.

    Now some generic equations:

    p(t)=a0+a1*t+     a2*t*t+     a3*t*t*t // circle center position
    n(t)= a1 +2.0*a2*t +3.0*a3*t*t // circle orientation
    • p,n,a0,a1,a2,a3 are vectors !!!
    • t is scalar

    Now add the known data

    I. t=0 -> p(0)=P0
    P0=a0
    a0=P0

    II. t=0 -> n(0)=N0
    N0=a1
    a1=N0

    III. t=1 -> p(1)=P1
    P1=a0+a1+a2+a3
    P1=P0+N0+a2+a3
    a2=P1-P0-N0-a3

    IV. t=1 -> n(1)=N1
    N1=a1+2.0*a2+3.0*a3
    N1=N0+2.0*(P1-P0-N0-a3)+3.0*a3
    a3=N1+N0-2.0*(P1-P0)

    III.
    a2=P1-P0-N0-(N1+N0-2.0*(P1-P0))
    a2=P1-P0-N0-N1-N0+2.0*(P1-P0)
    a2=P1-P0-N1+2.0*(P1-P0-N0)
    a2=3.0*(P1-P0)-N1-2.0*N0

    So if I did not make any silly mistake then coefficients are:

    a0=P0
    a1=N0
    a2=3.0*(P1-P0)-N1-2.0*N0
    a3=N1+N0-2.0*(P1-P0)

    So now just encode generic equations into some function with input parameter t and output p(t) and n(t) and/or render circle or tube segment and the call this in for loop for example like this:

    for (t=0.0;t<=1.0;t+=0.1) f(t);

[edit1] C++ implementation

//---------------------------------------------------------------------------
void glCircle3D(double *pos,double *nor,double r,bool _fill)
{
int i,n=36;
double a,da=divide(pi2,n),p[3],dp[3],x[3],y[3];
if (fabs(nor[0]-nor[1])>1e-6) vector_ld(x,nor[1],nor[0],nor[2]);
else if (fabs(nor[0]-nor[2])>1e-6) vector_ld(x,nor[2],nor[1],nor[0]);
else if (fabs(nor[1]-nor[2])>1e-6) vector_ld(x,nor[0],nor[2],nor[1]);
else vector_ld(x,1.0,0.0,0.0);
vector_mul(x,x,nor);
vector_mul(y,x,nor);
vector_len(x,x,r);
vector_len(y,y,r);
if (_fill)
{
glBegin(GL_TRIANGLE_FAN);
glVertex3dv(pos);
}
else glBegin(GL_LINE_STRIP);
for (a=0.0,i=0;i<=n;i++,a+=da)
{
vector_mul(dp,x,cos(a)); vector_add(p,pos,dp);
vector_mul(dp,y,sin(a)); vector_add(p,p ,dp);
glVertex3dv(p);
}
glEnd();
}
//---------------------------------------------------------------------------
void tube(double *P0,double *N0,double *P1,double *N1,double R)
{
int i;
double a0[3],a1[3],a2[3],a3[3],p[3],n[3],t,tt,ttt;
// compute coefficients
for (i=0;i<3;i++)
{
a0[i]=P0[i];
a1[i]=N0[i];
a2[i]=(3.0*(P1[i]-P0[i]))-N1[i]-(2.0*N0[i]);
a3[i]=N1[i]+N0[i]-2.0*(P1[i]-P0[i]);
}
// step through curve from t=0 to t=1
for (t=0.0;t<=1.0;t+=0.02)
{
tt=t*t;
ttt=tt*t;
// compute circle position and orientation
for (i=0;i<3;i++)
{
p[i]=a0[i]+(a1[i]*t)+(a2[i]*tt)+(a3[i]*ttt);
n[i]=a1[i]+(2.0*a2[i]*t)+(3.0*a3[i]*tt);
}
// render it
glCircle3D(p,n,R,false);
}
}
//---------------------------------------------------------------------------
void test()
{
// tube parameters
double P0[3]={-1.0, 0.0, 0.0},N0[3]={+1.0,-1.0, 0.0},p[3];
double P1[3]={+1.0,+1.0, 0.0},N1[3]={ 0.0,+1.0, 0.0};
// just normalize normals to size 3.1415...
vector_len(N0,N0,M_PI);
vector_len(N1,N1,M_PI);
// draw normals to visula confirmation of tube direction
glBegin(GL_LINES);
glColor3f(0.0,0.0,1.0); vector_add(p,P0,N0); glVertex3dv(P0); glVertex3dv(p);
glColor3f(0.0,0.0,1.0); vector_add(p,P1,N1); glVertex3dv(P1); glVertex3dv(p);
glEnd();
// render tube
glColor3f(1.0,1.0,1.0); tube(P0,N0,P1,N1,0.2);
}
//---------------------------------------------------------------------------

Visually it best looks when normals are of size M_PI (3.1415...) this is how it looks for the code above:

pipe C++

the code of mine use mine vector library so you just need to code functions like:

vector_ld(a,x,y,z); //a[]={ x,y,z }
vector_mul(a,b,c); //a[]=b[] x c[]
vector_mul(a,b,c); //a[]=b[] * c
vector_add(a,b,c); //a[]=b[] + c[]
vector_sub(a,b,c); //a[]=b[] - c[]
vector_len(a,b,c); //a[]=b[]* c / |b[]|

which is easy (hope I did not forget to copy something ...)...

How to visually connect 2 circles?

As the other answers so far slightly miss the correct solution, here is one that connects two circles of equal size:

using (Pen pen = new Pen(Color.Blue, radius)
{ EndCap = LineCap.Round, StartCap = LineCap.Round } )
g.DrawLine(pen, x1, y1, x2, y2);

Sample Image

Notes:

  • Usually is is good idea to set the smoothing mode of the graphics object to anti-alias..

  • To connect two circles of different sizes will take some math to calculate the four outer tangent points. From these one can get a polygon to fill or, if necessary one could create a GraphicsPath to fill, in case the color has an alpha < 1.

  • Jimi's comments point to a different solution that make use of GDI+ transformation capabilities.

  • Some of the answers or comments refer to the desired shape as an oval. While this ok in common speech, here, especially when geometry books are mentioned, this is wrong, as an oval will not have any straight lines.

  • As Jimi noted, what you call radius is really the diameter of the circles. I left the wrong term in the code but you should not!

Is point inside a circle sector

What you are looking for is the angle ψ below, to test if it is within certain limits (like +/- 90°)

fig1

This can be done by finding the angle ψ between the vectors B-A and D-B.

The angle between them is found using the following dot and cross product rules

fig2 fig3

and the pseudocode using the atan2(dy,dx) function that must programming environments have

v_1.x = B.x-A.x
v_1.y = B.y-A.y
v_2.x = D.x-B.x
v_2.y = D.y-B.y

ψ = atan2( (v_1.x*v_2.y - v_1.y*v_2.x), (v_1.x*v_2.x + v_1.y*v_2.y) )

Note that the result is within and covering all 4 quadrants

Now just check if abs(ψ) <= π/2.

Take a note of the definition of atan2 since some environments define it as atan2(dx,dy) and some as atan2(dy,dx), where the angle is measured such that dx=r*cos(ψ) and dy=r*sin(ψ).

And don't forget to check also the distance BD if it's within the radius of the circle.

triangulate center point with accuracy from set of coordinates

+1 for very interesting problem ... Here is my approach in C++

global tables and variables to store all needed:

const int    N=5;       // number of measurement circles
struct _circle { double x,y,r; _circle() { x=0; y=0; r=0.0; } } circle[N],circle0,circle1;
  • circle[] - measured positions
  • circle0 - original real position (unknown for you)
  • circle1 - computed more accurate position

here are my random measurements (copy your measured values instead):

int i;
double r,a,x0,x1,y0,y1;
// set real position
circle0.x=50.0;
circle0.y=50.0;
circle0.r=25.0;
// set meassured positions
Randomize();
for (i=0;i<N;i++)
{
r=circle0.r*(0.2+(0.3*double(Random(101))*0.01));
a= 2.0*M_PI* double(Random(101))*0.01;
circle[i]=circle0;
circle[i].x+=r*cos(a);
circle[i].y+=r*sin(a);
}

This is how to compute Average style position:

// compute more accurate position (average style)
circle1.x=0.0;
circle1.y=0.0;
circle1.r=circle0.r;
for (i=0;i<N;i++)
{
circle1.x+=circle[i].x;
circle1.y+=circle[i].y;
}
circle1.x/=N;
circle1.y/=N;
for (i=0;i<N;i++)
{
r=circle1.x-circle[i].x; r*=r;
a=circle1.y-circle[i].y; a*=a;
r=circle[i].r-sqrt(a+r);
// if (circle1.r>r) circle1.r=r; // best accuracy
if (circle1.r<r) circle1.r=r; // worse accuracy
}
  • chose what accuracy radius you want in two last ifs ...
  • position is based on average of all measured ones

This is how to compute Geometry style position:

// compute more accurate position (geometry style)
x0=circle[i].x; x1=x0;
y0=circle[i].y; y1=y0;
for (i=0;i<N;i++)
{
a=circle[i].x; if (x0>a) x0=a; if (x1<a) x1=a;
a=circle[i].y; if (y0>a) y0=a; if (y1<a) y1=a;
}
circle1.x=0.5*(x0+x1); x1-=x0; x1*=x1;
circle1.y=0.5*(y0+y1); y1-=y0; y1*=y1;
circle1.r=1.0*sqrt(x1+y1);
  • position is center of occupied area

And here are some previews of my code output:

accuracy overlap

Getting area of a line circle intersection

Claculate the green area, subtract it from the area of the circle.

https://en.wikipedia.org/wiki/Circular_segment

How to use a smooth curve to link points approximately distributing in a circle?

I think the key here is to note that you have to consider it as a parametrized curve in 2d, not just a 1d to 2d function. Furthermore since it should be something like a circle, you need an interpolation method that supports periodic boundaries. Here are two methods for which this applies:

% set up toy data
t = linspace(0, 2*pi, 10);
t = t(1:end-1);
a = 0.08;
b = 0.08;
x = cos(t+a*randn(size(t))) + b*randn(size(t));
y = sin(t+a*randn(size(t))) + b*randn(size(t));
plot(x, y, 'ok');

% fourier interpolation
z = x+1i*y;
y = interpft(z, 200);
hold on
plot(real(y), imag(y), '-.r')

% periodic spline interpolation
z = [z, z(1)];
n = numel(z);
t = 1:n;
pp = csape(t, z, 'periodic');
ts = linspace(1, n, 200);
y = ppval(pp, ts);;
plot(real(y), imag(y), ':b');

How to properly add gradually increasing/decreasing space between objects?

First, i took my function that was generating the intensity for each curves in [0 ; 2]
Then i scrapped the acceleration formula as it's unusable.
Now i'm using a basic algorithm to determine the maximum amount of circles i can place on a curve.

Now the way my script work is the following:
i first generate a stream (multiple circles that need to be clicked at high bpm)
this way i obtain the length of each curves (or segments) of the polyline.
i generate an intensity for each curve using the following function:

def generate_intensity(Circle_list: list = None, circle_space: int = None, Args: list = None):
curve_intensity = []
if not Args or Args[0] == "NewProfile":
prompt = True
while prompt:
max_duration_intensity = input("Choose the maximum amount of curve the change in intensity will occur for: ")
if max_duration_intensity.isdigit():
max_duration_intensity = int(max_duration_intensity)
prompt = False
prompt = True
while prompt:
intensity_change_odds = input("Choose the odds of occurence for changes in intensity (1-100): ")
if intensity_change_odds.isdigit():
intensity_change_odds = int(intensity_change_odds)
if 0 < intensity_change_odds <= 100:
prompt = False
prompt = True
while prompt:
min_intensity = input("Choose the lowest amount of spacing a circle will have: ")
if min_intensity.isdigit():
min_intensity = float(min_intensity)
if min_intensity < circle_space:
prompt = False
prompt = True
while prompt:
max_intensity = input("Choose the highest amount of spacing a circle will have: ")
if max_intensity.isdigit():
max_intensity = float(max_intensity)
if max_intensity > circle_space:
prompt = False
prompt = True
if Args:
if Args[0] == "NewProfile":
return [max_duration_intensity, intensity_change_odds, min_intensity, max_intensity]
elif Args[0] == "GenMap":
max_duration_intensity = Args[1]
intensity_change_odds = Args[2]
min_intensity = Args[3]
max_intensity = Args[4]
circle_space = ([min_intensity, circle_space, max_intensity] if not Args else [Args[0][3],circle_space,Args[0][4]])
count = 0
for idx, i in enumerate(Circle_list):
if idx == len(Circle_list) - 1:
if random.randint(0,100) < intensity_change_odds:
if random.randint(0,100) > 50:
curve_intensity.append(2)
else:
curve_intensity.append(0)
else:
curve_intensity.append(1)
if random.randint(0,100) < intensity_change_odds:
if random.randint(0,100) > 50:
curve_intensity.append(2)
count += 1
else:
curve_intensity.append(0)
count += 1
else:
if curve_intensity:
if curve_intensity[-1] == 2 and not count+1 > max_duration_intensity:
curve_intensity.append(2)
count += 1
continue
elif curve_intensity[-1] == 0 and not count+1 > max_duration_intensity:
curve_intensity.append(0)
count += 1
continue
elif count+1 > 2:
curve_intensity.append(1)
count = 0
continue
else:
curve_intensity.append(1)
else:
curve_intensity.append(1)
curve_intensity.reverse()
if curve_intensity.count(curve_intensity[0]) == len(curve_intensity):
print("Intensity didn't change")
return circle_space[1]
print("\n")
return [circle_space, curve_intensity]

with this, i obtain 2 list, one with the spacing i specified, and the second one is the list of randomly generated intensity.
from there i call another function taking into argument the polyline, the previously specified spacings and the generated intensity:

def acceleration_algorithm(polyline, circle_space, curve_intensity):
new_circle_spacing = []
for idx in range(len(polyline)): #repeat 4 times
spacing = []
Length = 0
best_spacing = 0
for p_idx in range(len(polyline[idx])-1): #repeat 1000 times / p_idx in [0 ; 1000]
# Create multiple list containing spacing going from circle_space[curve_intensity[idx-1]] to circle_space[curve_intensity[idx]]
spacing.append(np.linspace(circle_space[curve_intensity[idx]],circle_space[curve_intensity[idx+1]], p_idx).tolist())
# Sum distance to find length of curve
Length += abs(math.sqrt((polyline[idx][p_idx+1][0] - polyline[idx][p_idx][0]) ** 2 + (polyline [idx][p_idx+1][1] - polyline[idx][p_idx][1]) ** 2))
for s in range(len(spacing)): # probably has 1000 list in 1 list
length_left = Length # Make sure to reset length for each iteration
for dist in spacing[s]: # substract the specified int in spacing[s]
length_left -= dist
if length_left > 0:
best_spacing = s
else: # Since length < 0, use previous working index (best_spacing), could also jsut do `s-1`
if spacing[best_spacing] == []:
new_circle_spacing.append([circle_space[1]])
continue
new_circle_spacing.append(spacing[best_spacing])
break
return new_circle_spacing

with this, i obtain a list with the space between each circles that are going to be placed,
from there, i can Call Place_circles() again, and obtain the new stream:

def Place_circles(polyline, circle_space, cs, DoDrawCircle=True, surface=None):
Circle_list = []
curve = []
next_circle_space = None
dist = 0
for c in reversed(range(0, len(polyline))):
curve = []
if type(circle_space) == list:
iter_circle_space = iter(circle_space[c])
next_circle_space = next(iter_circle_space, circle_space[c][-1])
for p in reversed(range(len(polyline[c])-1)):
dist += math.sqrt((polyline[c][p+1][0] - polyline[c][p][0]) ** 2 + (polyline [c][p+1][1] - polyline[c][p][1]) ** 2)
if dist > (circle_space if type(circle_space) == int else next_circle_space):
dist = 0
curve.append(circles.circles(round(polyline[c][p][0]), round(polyline[c][p][1]), cs, DoDrawCircle, surface))
if type(circle_space) == list:
next_circle_space = next(iter_circle_space, circle_space[c][-1])
Circle_list.append(curve)
return Circle_list

the result is a stream with varying space between circles (so accelerating or decelerating), the only issue left to be fixed is pygame not updating the screen with the new set of circle after i call Place_circles(), but that's an issue i'm either going to try to fix myself or ask in another post

the final code for this feature can be found on my repo : https://github.com/Mrcubix/Osu-StreamGenerator/tree/Acceleration_v02

Dougnut chart: calculate outer circle's coordinates to get parallel gaps between slices

For @chux - Reinstate Monica's request, I'm posting here my final solution which relies heavily on his link in comment: Radius and central angle on a circular segment.

1.) Set the initial constants:

double pad = PADDING_VALUE;
double r_inner = INNER_RADIUS;
double r_outer = OUTER_RADIUS;

2.) Calculate the slice's starting point's coordinates (on the inner circle):

double alpha = CURRENT_SLICE_ANGLE;
double inner_angle = alpha + pad/2; // slice will begin with pad/2 offset

double x1 = r_inner * cos(inner_angle);
double y1 = r_inner * sin(inner_angle);

3.) Now calculate the previous slice's ending point's coordinates (also on the inner circle):

double x2 = r_inner * cos(inner_angle - pad); //easy, exactly angle-pad
double y2 = r_inner * sin(inner_angle - pad); //easy, exactly angle-pad

4.) Now I have the coords for the current slice's start and the previous slice's end. I want to keep a constant length between the 2 slices, and this length should be exactly the segment's length between (x1,y1) and (x2,y2). This is a right-angled triangle, and the segment's length I'm looking for is easily expressable with Pitagoras formula:

double hyp = sqrt(pow(x1-x2,2) + pow(y1-y2,2));

5.) This hyp is exactly the chord length (c) here:

Sample Image

6.) From the same Wikipedia page the Theta is expressible with chord length and radius:

double theta = 2 * asin(hyp / 2 / r_outer);

7.) I have to draw the outer arc with the help of theta and pad:

double outer_angle_correction = (pad - theta) / 2;

Applying this calculation results this:

Sample Image

This is a bit odd, as the gaps are a bit too large. But anyway these huge gaps were only used for demonstration, and after I changed them back to my initial intended values this is the result:

Sample Image

Perfectly parallel gaps between all slices without using any approximation - just pure math. Sweet.



Related Topics



Leave a reply



Submit