Algorithm for planning a rendezvous between two spaceships - physics
I'm trying to figure out an algorithm for setting up a rendezvous between two spaceships.
There is no gravity or drag. Both spaceships have a position and a velocity at the start. Spaceship B continues on its course with no acceleration, so spaceship A needs to accelerate to close the distance between them and then match velocities when it arrives at the position of spaceship B.
The spaceship can instantly change its direction of thrust, but can only use maximum acceleration or no acceleration at all. I also want a limit on the velocity difference between the spaceships during the maneuver.
I would like the output to be in the form of a number of trajectory legs, i.e: leg1: accelerate direction x for t1 seconds,
leg2: coast for t2 seconds,
leg3: accelerate direction y for t3 seconds.
I don't need an optimal solution, but I would like it to "look right".
I tried to make an impulse to equalize the velocities and add it to an impulse for moving towards spaceship B, but even though spaceship A ends up with the correct velocity, it fails to reach the targets position. I've tried the impulses by themselves and they seem to perform as expected, so I'm guessing it's the way I'm adding them together that is the problem. I don't know if I am implementing it incorrectly or if this approach simply won't work. I'm hoping someone with stronger math and physics skills can enlighten me.
Here is the code I am using:
// var velocityAdjustmentTime = (int)Math.Sqrt(2 * velocityDelta.Length / tp.Acceleration);
var velocityAdjustmentTime = (int)(velocityDelta.Length / tp.Acceleration);
var velocityAdjustVector = velocityDelta;
velocityAdjustVector.Normalize();
velocityAdjustVector *= tp.Acceleration;
var targetAccelerationDisplacement = new Vector3D(0, 0, 0); // TODO: Replace this with proper values.
Vector3D newPosition;
Vector3D newVelocity;
Vector3D targetNewPosition;
// Check if the formation and the target already have a paralell course with the same velocity.
if (velocityAdjustmentTime > 0)
{
// If not, calculate the position and velocity after the velocity has been aligned.
newPosition = tp.StartPosition + (tp.StartVelocity * velocityAdjustmentTime) + ((velocityAdjustVector * velocityAdjustmentTime * velocityAdjustmentTime) / 2);
newVelocity = tp.StartVelocity + velocityAdjustVector * velocityAdjustmentTime;
targetNewPosition = tp.TargetStartPosition + (tp.TargetStartVelocity * velocityAdjustmentTime) + targetAccelerationDisplacement;
}
else
{
// Else, new and old is the same.
newPosition = tp.StartPosition;
newVelocity = tp.StartVelocity;
targetNewPosition = tp.TargetStartPosition;
}
// Get the new direction from the position after velocity change.
var newDirection = targetNewPosition - newPosition;
// Changing this value moves the end position closer to the target. Thought it would be newdirection length, but then it doesn't reach the target.
var length = newDirection.Length;
// I don't think this value matters.
var speed = (int)(cruiseSpeed);
var legTimes = CalculateAccIdleDecLegs(tp.Acceleration, length, speed);
// Sets how much of the velocity change happens on the acceleration or deceleration legs.
var velFactorAcc = 1;
var velFactorDec = 1 - velFactorAcc;
// Make the acceleration vector.
accelerationVector = newDirection;
accelerationVector.Normalize();
accelerationVector *= legTimes[0] * tp.Acceleration;
accelerationVector += velocityDelta * velFactorAcc;
accelerationTime = (int)(accelerationVector.Length / tp.Acceleration);
accelerationVector.Normalize();
accelerationVector *= tp.Acceleration;
// Make the acceleration order.
accelerationLeg.Acceleration = accelerationVector;
accelerationLeg.Duration = accelerationTime;
// Make the deceleration vector.
decelerationVector = newDirection;
decelerationVector.Negate();
decelerationVector.Normalize();
decelerationVector *= legTimes[2] * tp.Acceleration;
decelerationVector += velocityDelta * velFactorDec;
decelerationTime = (int)(decelerationVector.Length / tp.Acceleration);
decelerationVector.Normalize();
decelerationVector *= tp.Acceleration;
// And deceleration order.
decelerationLeg.Acceleration = decelerationVector;
decelerationLeg.Duration = decelerationTime;
// Add the orders to the list.
trajectory.Add(accelerationLeg);
// Check if there is an idle leg in the middle...
if (legTimes[1] > 0)
{
// ... if so, make the order and add it to the list.
idleLeg.Duration = legTimes[1];
trajectory.Add(idleLeg);
}
// Add the deceleration order.
trajectory.Add(decelerationLeg);
And the function for calculating the approach legs:
private static int[] CalculateAccIdleDecLegs(double acceleration, double distance, int cruiseSpeed)
{
int[] legDurations = new int[3];
int accelerationTime;
int idleTime;
int decelerationTime;
// Calculate the max speed it's possible to accelerate before deceleration needs to begin.
var topSpeed = Math.Sqrt(acceleration * distance);
// If the cruise speed is higher than or equal to the possible top speed, the formation should accelerate to top speed and then decelerate.
if (cruiseSpeed >= topSpeed)
{
// Get the time to accelerate to the max velocity.
accelerationTime = (int)((topSpeed) / acceleration);
// Idle time is zero.
idleTime = 0;
// Get the deceleration time.
decelerationTime = (int)(topSpeed / acceleration);
}
// Else, the formation should accelerate to max velocity and then coast until it starts decelerating.
else
{
// Find the acceleration time.
accelerationTime = (int)((cruiseSpeed) / acceleration);
// Get the deceleration time.
decelerationTime = (int)(cruiseSpeed / acceleration);
// Calculate the distance traveled while accelerating.
var accelerationDistance = 0.5 * acceleration * accelerationTime * accelerationTime;
// Calculate the distance traveled while decelerating.
var decelerationDistance = 0.5 * acceleration * decelerationTime * decelerationTime;
// Add them together.
var thrustDistance = accelerationDistance + decelerationDistance;
// Find the idle distance.
var idleDistance = distance - thrustDistance;
// And the time to idle.
idleTime = (int)(idleDistance / cruiseSpeed);
}
legDurations[0] = accelerationTime;
legDurations[1] = idleTime;
legDurations[2] = decelerationTime;
return legDurations;
}
NEW VERSION:
Assume you have the initial positions and velocities xA0, vA0 and xB0, vB0 of spaceships A and B respectively. As you said, B moves with no acceleration and with constant velocity vB0. Therefore, it travels uniformly along a straight line. Its motion is described as: xB = xB0 + t*vB0. Spaceship A can turn on and off an acceleration of constant magnitude a0 but can change its direction as it sees fit. The velocity of A should not exceed certain value v_max > 0.
Since spaceship B travels uniformly, along a straight line with constant velocity vB0, it actually defines an inertial coordinate system. In other words, if we translate the original coordinate system and attach it to B, the new system travels with constant velocity along a straight line and is therefore also inertial. The transformation is Galilean, so one can define the following change of coordinates (in both directions)
y = x - xB0 - t*vB0
u = v - vB0
x = y + xB0 + t*vB0
v = u + vB0
in particular, for B for any moment of time t we get
yB = xB - xB0 - t*vB0 = xB0 + t*vB0 - xB0 - t*vB0 = 0``
At time t=0,
yA0 = xA0 - xB0
uA0 = vA0 - vB0
We are aiming to design the control in this new coordinate system and them move it back into the original one. So let us switch coordinates:
y = x - xB
u = v - vB0
So in this new inertial coordinate system we are solving a problem of control theory and to engineer a good control, we would use as a Lyapunov function (a function that allows us to guarantee certain stable behavior and design the proper expression for the acceleration a) the magnitude of the velocity squared L = norm(u)^2. We want to design acceleration a so that the Lyapunov function in the initial phase of the motion monotonically and steadily decreases while the velocity reorients appropriately.
Define the unit vector
L_unit = cross(x0A - xB0, v0A - vB0) / norm(cross(x0A - xB0, v0A - vB0))
Let in the coordinate system attached to B the motion of A satisfy the system of ordinary differential equations (these equations in both systems are Newton's, because both systems are inertial):
dy/dt = u
du/dt = - a0 * (u - cross(L_unit, u)) / norm(u - cross(L_unit, u))
In other words, the acceleration is set to
a = - a0 * (u - cross(L_unit, u)) / norm(u - cross(L_unit, u))
Observe that by design norm(a) = a0. Because the vectors u and cross(L_unit, u) are orthogonal and of equal magnitude (simply cross(L_unit, u) is the ninety degree rotation of vector u), the denominator simplifies to
norm(u - cross(L_unit, u)) = sqrt( norm(u - cross(L_unit, u))^2 )
= sqrt( norm(u)^2 + norm(L_unit, u)^2 )
= sqrt( norm(u)^2 + norm(L_unit)^2*norm(u)^2 )
= sqrt( norm(u)^2 + norm(u)^2)
= sqrt(2) * norm(u)
So the system of differential equations simplifies to
dy/dt = u
du/dt = -(a0/sqrt(2)) * u/norm(u) + (a0/sqrt(2)) * cross(L_unit, u)) / norm(u)
The system is designed so that A always moves in the plane passing thorugh the origin B and perpendicular to the vector L_unit.
Becauseu and cross(L_unit, u) are perpendicular, their dot product is 0, which allows us to calculate the time-derivative of the lyapunov function along the solutions to the system above (u' means transpose of column-vector u):
d/dt( L ) = d/dt( norm(u)^2 ) = d/dt( u' * u ) = u' * du/dt
= u' * ( -(a0/sqrt(2)) * u/norm(u)
+ (a0/sqrt(2)) * cross(L_unit, u)) / norm(u) )
= -(a0/sqrt(2)) * u'*u / norm(u)
+ (a0/sqrt(2)) * u'*cross(L_unit, u)) / norm(u)
= -(a0/sqrt(2)) * norm(u)^2 / norm(u)
= -(a0/sqrt(2)) * norm(u)
= - (a0/sqrt(2)) * sqrt(L)
d/dt( L ) = -(a0/sqrt(2)) * sqrt(L) < 0
which means that norm(u) decreases with time to 0, as desired.
The system of differential equations, that governs the motion, looks initially non-linear but can be linearized and explicitly solvaed. However, for simplicity, I have decided to integrate it numerically.
The system of differential equations, that governs the motion, looks initially non-linear but can be linearized and explicitly solved. However, for simplicity, I have decided to integrate it numerically. To do that, I have chosen a geometric integrator method, where the system is split into two explicitly solvable systems, whose solutions are combined together to give (a very good approximation of) the solution to the original system. The systems are:
dy/dt = u / 2
du/dt = -(a0/sqrt(2)) u / norm(u)
and
dy/dt = u / 2
du/dt = (a0/sqrt(2)) cross(L_unit, u) / norm(u)
Initially, the second system is nonlinear, however after we calculate:
d/dt(norm(u)*2) = d/dt (dot(u, u)) = 2 * dot(u, du/dt)
= 2 * dot(u, (a0/sqrt(2)) * cross(L_unit , u))
= 2 * (a0/sqrt(2)) * dot(u, cross(L_unit , u))
= 0
we conclude that during the motion defined by this system, the magnitude of the
velocity is constant, i.e. norm(u) = norm(u0) where u0 = u(0). Thus, the systems, together with their solutions, now look like:
First system:
dy/dt = u / 2
du/dt = -(a0/sqrt(2)) u / norm(u)
Solution:
y(t) = y0 + h * u0/2 - t^2 * a0 * u0 / (4*sqrt(2)*norm(u0));
u(t) = u - t * a0 * u0 / (sqrt(2)*norm(u0));
and
Second system:
dy/dt = u / 2
du/dt = (a0/(sqrt(2)*norm(u0))) cross(L_unit, u)
Solution:
y(t) = y0 + (sqrt(2)*norm(u0)/a0) *( cross(L_unit, u0)
+ sin( t * a0/(sqrt(2)*norm(u0)) ) * u0
- cos( t *a0/(sqrt(2)*norm(u0)) ) * cross(L_unit, u0) )
u(t) = cos( t *a0/(sqrt(2)*norm(u0)) ) * u0
+ sin( t *a0/(sqrt(2)*norm(u0)) ) * cross(L_unit, u0)
The solution to the original system can be approximated as follows. Select a time step h. Then if at time t the spaceship's position and velocity have been calculated to be y, u, the updated spaceship's position and velocity at time t + h can be calculated by first letting the ship move along the solution of the second system starting from y, u for time h/2, then move along the solution of the first system for time h and then move along the solution of the second system for time h/2.
function main()
h = 0.3;
a0 = 0.1;
u_max = .8; % velocity threshold
xB0 = [0; 0; 0];
vB0 = [-1; 2; 0];
xA0 = [ 7; 12; 0] + xB0;
vA0 = [1; 5; 0]/7;
%vA0 = [2; -1; 0];
L_unit = cross(xA0 - xB0, vA0 - vB0);
L_unit = L_unit / norm(L_unit);
t = 0;
xB = xB0;
x = xA0;
v = vA0;
hold on
grid on
%axis([-200 20 -100 350])
plot(0, 0, 'bo')
% STEP 0 (the motion as described in the text above):
n = floor(sqrt(2)*norm(vA0 - vB0)/(a0*h));
for i=1:n
[t, x, v, xB] = E(t, x, v, xB, vB0, a0, L_unit, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
u = v - vB0;
norm_u = norm(u);
% short additional decceleration so that A attains velocity v = vB0
t0 = t + norm_u/a0;
n = floor((t0 - t)/h);
a = - a0 * u / norm_u;
for i=1:n
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, t0-t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
% STEP 1 (uniform acceleration of magnitude a0):
v = vB0;
a = x-xB;
norm_y0 = norm(a);
a = - a0 * a / norm_y0;
%t2 = t1 + sqrt( norm_y/a0 );
accel_time = min( u_max/a0, sqrt( norm_y0/a0 ) );
t1 = t0 + accel_time;
n = floor((t1 - t0)/h);
for i=1:n
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'bo');
plot(xB(1), xB(2), 'ro');
pause(0.1)
end
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, t1-t);
plot(x(1), x(2), 'bo');
plot(xB(1), xB(2), 'ro');
pause(0.1)
% STEP 2 (uniform straight-line motion):
norm_y1 = norm(x-xB);
norm_y12 = max(0, norm_y0 - 2*(norm_y0 - norm_y1));
t12 = norm_y12 / norm(v-vB0)
t = t + t12
n12 = floor(t12/h)
for i=1:n12
x = x + h*v;
xB = xB + h*vB0;
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
x = x + (t12-n12*h)*v;
xB = xB + (t12-n12*h)*vB0;
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
% STEP 3 (uniform deceleration of magnitude a0, symmetric to STEP 1):
a = -a;
for i=1:n % t2 + (t2-t1)
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'bo');
plot(xB(1), xB(2), 'ro');
pause(0.1)
end
[t, x, v, xB] = ET(t, x, v, xB, vB0, a, t0+t12+2*accel_time-t);
plot(x(1), x(2), 'bo');
plot(xB(1), xB(2), 'ro');
pause(0.1)
norm(x-xB)
norm(v-vB0)
end
Here are the additional functions that are used in the main code above:
% change of coordinates from world coordinates x, v
% to coordinates y, u from spaship B's point of view:
function [y, u] = change(x, v, xB, vB0)
y = x - xB;
u = v - vB0;
end
% inverse chage of coordinates from y, u to x, v
function [x, v] = inv_change(y, u, xB, vB0)
x = y + xB;
v = u + vB0;
end
% solution to the second system of differential equations for a step h:
function [y_out, u_out] = R(y, u, a0, L_unit, h)
omega = a0 / (sqrt(2) * norm(u));
L_x_u = cross(L_unit, u);
cos_omega_h = cos(omega*h);
sin_omega_h = sin(omega*h);
omega = 2*omega;
y_out = y + (L_x_u ...
+ sin_omega_h * u - cos_omega_h * L_x_u) / omega;
u_out = cos_omega_h * u + sin_omega_h * L_x_u;
end
% solution to the first system of differential equations for a step h:
function [y_out, u_out] = T(y, u, a0, h)
sqrt_2 = sqrt(2);
u_unit = u / norm(u);
y_out = y + h * u/2 - h^2 * a0 * u_unit/ (4*sqrt_2);
u_out = u - h * a0 * u_unit / sqrt_2;
end
% approximate solution of the original system of differential equations for step h
% i.e. the sum of furst and second systems of differential equations:
function [t_out, x_out, v_out, xB_out] = E(t, x, v, xB, vB0, a0, L_unit, h)
t_out = t + h;
[y, u] = change(x, v, xB, vB0);
[y, u] = R(y, u, a0, L_unit, h/2);
[y, u] = T(y, u, a0, h);
[y, u] = R(y, u, a0, L_unit, h/2);
xB_out = xB + h*vB0;
[x_out, v_out] = inv_change(y, u, xB_out, vB0);
end
% straight-line motion with constant acceleration:
function [t_out, x_out, v_out, xB_out] = ET(t, x, v, xB, vB0, a, h)
t_out = t + h;
[y, u] = change(x, v, xB, vB0);
y = y + h * u + h^2 * a / 2;
u = u + h * a;
xB_out = xB + h*vB0;
[x_out, v_out] = inv_change(y, u, xB_out, vB0);
end
OLDER VERSION:
I developed two models. Both models are initially described in the moving with B inertial frame of reference y, u (see my previous answers) and then the coordinates are transformed into the original ones x, v. I designed the control based on the function norm(u)^2 as a Lyapunov function, so that in the first step of the algorithm, the acceleration is designed so that the Lyapunov function norm(u)^2 decreases steadily. In the first version, the speed of decrease is quadratic, but the model is easier to integrate, while in the second version, the speed of decrease is exponential, but the model requires Runge-Kutta integration. And I haven't quite tuned it well. I think Version 1 should looks good.
Take L_unit = cross(y0, u0) / norm(cross(y0, u0)).
Version 1: The model is:
dy/dt = y
du/dt = - a0 * (u + cross(L_unit, u)) / norm(u + cross(L_unit, u))
= - a0 * (u + cross(L_unit, u)) / (norm(u)*sqrt(1 + norm(L_unit)^2))
= - a0 * (u + cross(L_unit, u)) / (sqrt(2) * norm(u))
To integrate it, split it into a pair of systems:
dy/dt = y
du/dt = - a0 * u / norm(u)
dy/dt = y
du/dt = - a0 * cross(L_unit, u) / norm(u0) (see previous answers)
and integrate them one after the other for small increments of h time intervals, and then go back and forth between these two systems consecutively. I experimented with some Matlab code:
function main()
h = 0.3;
a0 = 0.1;
xB0 = [0; 0; 0];
vB0 = [-1; 2; 0];
xA0 = [ 7; 12; 0] + xB0;
vA0 = [2; -1; 0];
L_unit = cross(xA0 - xB0, vA0 - vB0);
L_unit = L_unit / norm(L_unit);
t = 0;
xB = xB0;
x = xA0;
v = vA0;
hold on
grid on
%axis([-200 20 -100 350])
plot(0, 0, 'bo')
n = floor(2*norm(v - vB0)/(h*a0));
for i=1:n
[t, x, v, xB] = R(t, x, v, xB, vB0, a0, L_unit, h/2);
a = - a0 * (v - vB0) / norm(v - vB0);
[t, x, v, xB] = T(t, x, v, xB, vB0, a, h/2);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
t1 = t + norm(v - vB0)/a0;
n = floor((t1 - t)/h);
a = - a0 * (v - vB0) / norm(v - vB0);
for i=1:n
[t, x, v, xB] = T(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, a, t1-t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
t2 = t1 + sqrt( norm(x - xB)/a0 );
n = floor((t2 - t1)/h);
a = - a0 * (x - xB) / norm(x - xB);
v = vB0;
for i=1:n
[t, x, v, xB] = T(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, a, t2-t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
for i=1:n % t2 + (t2-t1)
[t, x, v, xB] = T(t, x, v, xB, vB0, -a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, -a, 2*t2 - t1 -t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
where the relevant functions are:
function [t_out, y_out, u_out] = R1(t, y, u, a0, L_unit, h)
t_out = t + h;
norm_u = norm(u);
R = norm_u^2 / a0;
cos_omega_h = cos(a0 * h / norm_u);
sin_omega_h = sin(a0 * h / norm_u);
u_unit = u / norm_u;
y_out = y + R * cross(L_unit, u_unit) ...
+ R * sin_omega_h * u_unit ...
- R * cos_omega_h * cross(L_unit, u_unit);
u_out = norm_u * sin_omega_h * cross(L_unit, u_unit) ...
+ norm_u * cos_omega_h * u_unit;
end
function [t_out, x_out, v_out, xB_out] = R(t, x, v, xB, vB0, a0, L_unit, h)
[t_out, y_out, u_out] = R1(t, x - xB, v - vB0, a0, L_unit, h);
xB_out = xB + h * vB0;
x_out = y_out + xB_out;
v_out = u_out + vB0;
end
function [t_out, y_out, u_out] = T1(t, y, u, a, h)
t_out = t + h;
u_out = u + h * a;
y_out = y + h * u + h^2 * a / 2;
end
function [t_out, x_out, v_out, xB_out] = T(t, x, v, xB, vB0, a, h)
[t_out, y_out, u_out] = T1(t, x - xB, v - vB0, a, h);
xB_out = xB + h * vB0;
x_out = y_out + xB_out;
v_out = u_out + vB0;
end
Version 2: The model is:
0 < k0 < 2 * a0 / norm(u0)
dy/dt = y
du/dt = - k0 * u / 2 + sqrt(a0^2 - k0^2 * norm_u^2 / 4) * cross(L_unit, u/norm_u);
Matlab code:
function main()
h = 0.3;
a0 = 0.1;
xB0 = [0; 0; 0];
vB0 = [-1; 2; 0];
xA0 = [ 7; 12; 0] + xB0;
vA0 = [2; -1; 0];
k0 = a0/norm(vA0-vB0);
L_unit = cross(xA0 - xB0, vA0 - vB0);
L_unit = L_unit / norm(L_unit);
t = 0;
xB = xB0;
x = xA0;
v = vA0;
hold on
grid on
%axis([-200 20 -100 350])
plot(0, 0, 'bo')
n = floor(2*norm(v - vB0)/(h*a0)); % this needs to be improved
for i=1:n
[t, x, v, xB] = F_step(t, x, v, xB, vB0, a0, L_unit, k0, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
t1 = t + norm(v - vB0)/a0;
n = floor((t1 - t)/h);
a = - a0 * (v - vB0) / norm(v - vB0);
for i=1:n
[t, x, v, xB] = T(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, a, t1-t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
t2 = t1 + sqrt( norm(x - xB)/a0 );
n = floor((t2 - t1)/h);
a = - a0 * (x - xB) / norm(x - xB);
v = vB0;
for i=1:n
[t, x, v, xB] = T(t, x, v, xB, vB0, a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, a, t2-t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
for i=1:n % t2 + (t2-t1)
[t, x, v, xB] = T(t, x, v, xB, vB0, -a, h);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
[t, x, v, xB] = T(t, x, v, xB, vB0, -a, 2*t2 - t1 -t);
plot(x(1), x(2), 'ro');
plot(xB(1), xB(2), 'bo');
pause(0.1)
end
where the relevant functions are:
function [dydt, dudt] = F1(u, a0, L_unit, k0)
norm_u = norm(u);
dydt = u;
dudt = - k0 * u / 2 + sqrt(a0^2 - k0^2 * norm_u^2/4) * cross(L_unit, u/norm_u);
end
function [t_out, y_out, u_out] = F1_step(t, y, u, a0, L_unit, k0, h)
t_out = t + h;
[z1, w1] = F1(u, a0, L_unit, k0);
[z2, w2] = F1(u + h * w1/2, a0, L_unit, k0);
[z3, w3] = F1(u + h * w2/2, a0, L_unit, k0);
[z4, w4] = F1(u + h * w3, a0, L_unit, k0);
y_out = y + h*(z1 + 2*z2 + 2*z3 + z4)/6;
u_out = u + h*(w1 + 2*w2 + 2*w3 + w4)/6;
end
function [t_out, x_out, v_out, xB_out] = F_step(t, x, v, xB, vB0, a0, L_unit, k0, h)
[t_out, x_out, v_out] = F1_step(t, x-xB, v-vB0, a0, L_unit, k0, h);
xB_out = xB + h * vB0;
x_out = x_out + xB_out;
v_out = v_out + vB0;
end
function [t_out, y_out, u_out] = T1(t, y, u, a, h)
t_out = t + h;
u_out = u + h * a;
y_out = y + h * u + h^2 * a / 2;
end
function [t_out, x_out, v_out, xB_out] = T(t, x, v, xB, vB0, a, h)
[t_out, y_out, u_out] = T1(t, x - xB, v - vB0, a, h);
xB_out = xB + h * vB0;
x_out = y_out + xB_out;
v_out = u_out + vB0;
end
I have tried to outline a somewhat simple approach, back of the envelope so to say, divided into four simple steps.
Assume you have the initial positions and velocitiesxA0, vA0 and xB0, vB0 of spaceship A and B respectively. As you said, B moves with no acceleration and with constant velocity vB0. Therefore, it travels uniformly along a straight line. Its motion is described as:
xB = xB0 + t*vB0
Spaceship A can turn on and off an acceleration of constant magnitude a0 but can change its direction as it sees fit.
I really hope that your velocitiy limit satisfies norm(vA0 - vB0) < v_max otherwise, the acceleration control you have to construct becomes more complex.
Step 1: Kill the difference between the velocities of A and B. Apply constant acceleration
a = a0 *(vB0 - vA0) / norm(vB0 - vA0)
to spaceship A. Then, the positions and the velocities of A and B change with time as follows:
xA = xA0 + t*vA0 + t^2*a0*(vB0 - vA0)/(2*norm(vB0 - vA0))
vA = vA0 + t*a0*(vB0 - vA0)/norm(vB0 - vA0)
xB = xB0 + t*vB0
vB = vB0
At time t1 = norm(vB0 - vA0)/a0 the velocity of spaceship A is vB0 which is equal in magnitude and direction to the velocity of spaceship B. At t1 if A turns off its acceleration and keeps it off, it will travel parallel to B, just with an offset in space.
Explanation: (not needed for the algorithm, but explains the calculations used in the next steps)
Since spaceship B travels uniformly, along a straight line with constant velocity vB0, it actually defines an inertial coordinate system. In other words, if we translate the original coordinate system and attach it to B, the new system travels with constant velocity along a straight line and is therefore also inertial. The transformation is Galilean, so one can define the following change of coordinates (in both directions)
y = x - xB0 - t*vB0
u = v - vB0
x = y + xB0 + t*vB0
v = u + vB0
At time t1 from step 1, the positions of the two spaceships are
xA1 = xA0 + t1*vA0 + t1^2*a0*(vB0 - vA0)/(2*norm(vB0 - vA0))
xB1 = xB0 + t*vB0
and their velocities are vA1 = vB1 = vB0. Thus
yA1 = xA1 - xB0 - t1*vB0
yB1 = xB1 - xB0 - t1*vB0 = xB0 + t1*vB0 - xB0 - t1*vB0 = 0
In this coordinate system, if at time t1 A turns off its acceleration and keeps it off, it will be just stationary, i.e. its position yA1 will not change with time. Now, all we have to do is move A from point yA1 to 0 along the straight-line segment AB, defined by the vector - yA1 = vector(AB) (pointing from A to the origin B). The idea is that now A can simply move with constant acceleration along AB for some time (t2-t1), gaining some velocity uA2 which does not exceed your velocity limit morm(uA2 + vB0) < v_max, then turn off the acceleration and fly for some period of time (t3-t2), which is to be determined, with velocity uA2, and finally turn on decceleration along AB for time (t4-t3) = (t2-t1), and at time t4 the A and B meet and the velocity of A is 0 (in the new coordinate system, the one flying with B). Which means the two ships are at the same location and have the same velocity (as a vector) in the original coordinate system.
Now,
yA = yA1 - (t-t1)^2*a0*yA1/(2*norm(yA1))
uA = (t-t1)*a0*yA1/norm(yA1)
so at t2 (all points yA1, yA2, yA3 and 0 are collinear):
yA2 = yA1 - (t2-t1)^2*a0*yA1/(2*norm(yA1)) = (norm(yA1)-(t2-t1)^2*a0/(2*norm(yA1))) * yA1
uA2 = (t2-t1)*a0*yA1/norm(yA1)
norm(yA2 - yA1) = norm( yA1 - (t2-t1)^2*a0*yA1/(2*norm(yA1)) - yA1 )
= norm(- (t2-t1)^2*a0*yA1/(2*norm(yA1)))
= (t2-t1)^2*(a0/2)*norm(yA1/norm(yA1))
= (t2-t1)^2*a0/2
norm(yA1) = norm(yA2 - yA1) + norm(yA3 - yA2) + norm(0 - yA3)
norm(yA3 - yA2) = norm(yA1) - norm(yA2 - yA1) - norm(0 - yA3)
= norm(yA1) - (t2-t1)^2*a0
(t3-t2) = norm(yA3 - yA2) / norm(uA2) = ( norm(yA1) - (t2-t1)^2*a0 )/norm(uA2)
Now, let us return to the original coordinate system.
yA1 = xA1 - xB1
uA2 = vA2 - vB0
(t3-t2) = ( norm(xA1 - xB1) - (t2-t1)^2*a0 )/norm(vA2 - vB0)
so the important calculation here is: as soon as you choose your t2, you get to calculate
t3 = t2 + ( norm(xA1 - xB1) - (t2-t1)^2*a0 )/norm(vA2 - vB0)
Step 2: As it was mentioned already, at time t1 from step 1, the positions of the two spaceships are
xA1 = xA0 + t1*vA0 + t1^2*a0*(vB0 - vA0)/(2*norm(vB0 - vA0))
xB1 = xB0 + t*vB0
and their velocities are vA1 = vB1 = vB0.
At time t1 apply acceleration a = a0*(xB1 - xA1)/norm(xB1 - xA1). Then, the positions and the velocities of A and B change with time as follows:
xA = xA1 + (t-t1)*vB0 + (t-t1)^2*a0*(xB1 - xA1)/(2*norm(xB1 - xA1))
vA = vB0 + (t-t1)*a0*(xB1 - xA1)/norm(xB1 - xA1)
xB = xB1 + (t-t1)*vB0 or if you prefer xB = xB0 + t*vB0
vB = vB0
Pick any t2 that satisfies
t2 <= t1 + sqrt( norm(xA1 - xB1)/a0 ) (the time to get to the middle of ``AB`` accelerating)
and such that it satisfies
norm( vB0 - (t2 - t1)*a0*(xA1 - xB1)/norm(xA1 - xB1) ) < v_max
Then at time t2 you get the positions an velocities
xA2 = xA1 + (t2-t1)*vB0 + (t2-t1)^2*a0*(xB1 - xA1)/(2*norm(xB1 - xA1))
vA2 = vB0 + (t2-t1)*a0*(xB1 - xA1)/norm(xB1 - xA1)
xB2 = xB1 + (t2-t1)*vB0 or if you prefer xB2 = xB0 + t2*vB0
vB2 = vB0
Step 3: Calculate the next time-moment
t3 = t2 + ( norm(xA1 - xB1) - (t2-t1)^2*a0 )/norm(vA2 - vB0)
and since A moves with constant velocity vA2 along a straight line:
xA3 = xA2 + (t3-t2)*vA2
vA3 = vA2
xB3 = xB2 + (t3-t2)*vB0 or if you prefer xB3 = xB0 + t3*vB0
vB3 = vB0
Step 4: This is the final stretch, when A deccelerates to meet with B:
t4 = t3 + (t2-t1)
At time t3 apply acceleration a = a0*(xA1 - xB1)/norm(xA1 - XB1), exactly opposite to the one from step 2. Then, the positions and the velocities of A and B change with time as follows:
xA = xA3 + (t-t3)*vB3 + (t-t3)^2*a0*(xA1 - xB1)/(2*norm(xA1 - xB1))
vA = vB3 + (t-t3)*a0*(xA1 - xB1)/norm(xA1 - xB1)
xB = xB3 + (t-t3)*vB0 or if you prefer xB = xB0 + t*vB0
vB = vB0
and for t4 we should have
xA4 = xB4 and vA4 = vB0
Now I realize there is a fair amount of details, so it possible I have some typos and possibly errors. However, the idea looks sound to me but I advise you to redo some of the calculations, just to be sure.
Assume you have the initial positions and velocities xA0, vA0 and xB0, vB0 of spaceships A and B respectively. As you said, B moves with no acceleration and with constant velocity vB0. Therefore, it travels uniformly along a straight line. Its motion is described as: xB = xB0 + t*vB0. Spaceship A can turn on and off an acceleration of constant magnitude a0 but can change its direction as it sees fit.
Since spaceship B travels uniformly, along a straight line with constant velocity vB0, it actually defines an inertial coordinate system. In other words, if we translate the original coordinate system and attach it to B, the new system travels with constant velocity along a straight line and is therefore also inertial. The transformation is Galilean, so one can define the following change of coordinates (in both directions)
y = x - xB0 - t*vB0
u = v - vB0
x = y + xB0 + t*vB0
v = u + vB0
in particular, for B for any moment of time t we get
yB = xB - xB0 - t*vB0 = xB0 + t*vB0 - xB0 - t*vB0 = 0``
At time t=0,
yA0 = xA0 - xB0
uA0 = vA0 - vB0
So we are going to design the control in this new coordinate system and them move it back into the original one. First, spaceship A is going move so that its velocity has always the same magnitude norm(uA) = norm(uA0) but its direction changes uniformly. To achieve that, one can simply take the cross-product vector
L0 = cross(yA0, uA0) / ( norm( cross(yA0, uA0) ) * norm(uA0) )
and at each moment of time t apply acceleration
a = a0 * cross(L0, uA)
This means that the law of motion of A satisfies the differential equations
dyA/dt = uA
duA/dt = a0 * cross(L0 , uA)
then
d/dt (dot(uA, uA)) = 2 * dot(uA, duA/dt) = 2 * dot(uA, a0 * cross(L0 , uA))
= 2 * a0 * dot(uA, cross(L0 , uA))
= 0
which is possible only when norm(uA)^2 = dot(uA, uA) = norm(uA0), i.e. the magnitude norm(uA) = norm(uA0) for all t is constant.
Let us check the norm of the acceleration's magnitude:
norm(a) = a0 * norm( cross(L0, uA)) = a0 * norm(L0) * norm(uA)
= a0 * norm( cross(yA0, uA0)/( norm( cross(yA0, uA0) )*norm(uA0) ) )*norm(uA0)
= a0 * norm( cross(yA0, uA0) )/( norm( cross(yA0, uA0) )*norm(uA0) ) )*norm(uA0)
= a0
Since norm(uA) = norm(uA0) = const the tip of the velocity of A, drawn as a vector uA from the origin B, always lies on the sphere norm(uA) = norm(uA0) centered at the origin. At the same time
d/dt ( dot(L0, uA) ) = dot(L0, duA/dt) = a0 * dot(L0, cross(L0, uA)) = 0
which means that
dot(L0, uA) = const = dot(L0, uA0) = 0
hence uA always lies on a plane perpendicular to vector L0 and passing through the origin. Thus, uA points to the intersection of the said plane with the sphere norm(uA) = norm(uA0), i.e. uA traverses a circle. In other words, the equation
duA/dt = a0 cross(L0, uA)
defines a rotation around the origin of vector uA in a plane through the origin and perpendicular to L0. Next, take
dyA/dt - a0*cross(L0, yA) = uA - a0*cross(L0, yA)
and differentiate it with respect to t:
duA/dt - a0*cross(L0, dyA/dt) = duA/dt - a0*cross(L0, uA) = 0
which means that there exists a constant vector such that dyA/dt - a0*cross(L0, yA) = const_vect and we can rewrite this last equation as
dyA/dt = a0*cross(L0, yA - cA)
and even like
d/dt( yA - cA ) = a0*cross(L0, yA - cA)
which just by the same arguments as the ones for uA implies that yA - cA traverses a circle centered at the origin and in a plane perpendicular to L0. Consequently, yA traverses a circle in the plane through the origin, perpendicular to L0 and centered at cA. One just needs to find the radius and the center of the circle. Then the motion of A under the equations
dyA/dt = uA
duA/dt = a0* cross(L0, uA)
reduces to the equation
dyA/dt = a0 * cross(L0, yA - cA)
yA(0) = yA0
In order to find the radius R, we set time t=0:
uA0 = a0 * cross(L0, yA0 - cA)
so
norm(uA0) = a0 * norm(cross(L0, yA0 - cA)) = a0 * norm(L0) * norm(yA0 - cA)
= a0 * norm(L0) * R
norm(L0) = 1 / norm(uA0)
R = norm(uA0)^2 / a0
The center is then along the vector perpendicular to both uA0 and L0 , so
cA = yA0 + R * cross(L0, uA0) / (norm(L0)*norm(uA0))
Then, we can set up a 2D coordinate system in the plane in which the motion occurs by choosing origin yA0 and unit perpendicular vectors uA0/norm(uA0) and -cross(L0, uA0) / (norm(L0)*norm(uA0)). So the motion of A in the coordinate system moving uniformly in a straight line with B can be described as
yA(t) = yA0 + R * sin(a0 * t / norm(L0)) * uA0 / norm(uA0)
- R * cos(a0 * t / norm(L0)) * cross(L0, uA0) / (norm(L0)*norm(uA0))
which is the solution to the initial value problem:
dyA/dt = uA0
duA/dt = a0 * cross(L0, uA)
yA(0) = yA0
uA(0) = uA0
So my new suggestion is to incorporate
Step 0: For a time period t from 0 to t0 apply the following acceleration and motion, which rotates the direction of the velocity vector of A:
yA0 = xA0 - xB0
uA0 = vA0 - vB0
L0 = cross(yA0, uA0) / ( norm( cross(yA0, uA0) ) * norm(uA0) )
a = a0 * cross(L0, uA0)
R = norm(uA0)^2 / a0
yA(t) = yA0 + R * cos(a0*t/norm(uA0)) / (norm(L0)*norm(uA0))
+ R * sin(a0*t/norm(uA0)) * uA0/norm(uA0)
- R * cos(a0*t/norm(uA0)) * cross(L0, uA0) / (norm(L0)*norm(uA0))
xA(t) = yA(t) + xB0 + t * vB0 =
= xA0 + t * vB0 + R * cos(a0*t/norm(uA0)) / (norm(L0)*norm(uA0))
+ R * sin(a0*t/norm(uA0)) * uA0/norm(uA0)
- R * cos(a0*t/norm(uA0)) * cross(L0, uA0) / (norm(L0)*norm(uA0))
until a moment of time t0 chosen so that the velocity's direction vA(t) is at a better position relative to vB0 so that from moment t0 on, you can apply the four steps outlined in my previous answer. Of course you can also use this new circular motion control to make your own combination that you like better.
Related
Mask values inside given path (triangle, square etc) for a contourf plot
I am trying to mask specific locations (triangles, squares) for a contourf plot. I can do the mask based on the Z values but finding it difficult to get it work based on x and y values. For the MWE below, I want to create a mask between given X,Y values (triangle or square). Lets say for the example below, I want to mask values inside triangle formed between points (0,0),(2,0),(0,2). I want to basically be able to provide an enclosed path and mask everything in between those values. I have tried the approach here but I have to provide the logic for individual X and Y values which becomes cumbersome for a complicated path. import numpy as np import matplotlib.pyplot as plt origin = 'lower' delta = 0.025 x = y = np.arange(-3.0, 3.01, delta) X, Y = np.meshgrid(x, y) Z1 = np.exp(-X**2 - Y**2) Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2) Z = (Z1 - Z2) * 2 fig1, ax2 = plt.subplots(constrained_layout=True) CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.viridis, origin=origin,extend='both') ax2.set_title('Random Plot') ax2.set_xlabel('X Axis') ax2.set_ylabel('Y Axis') cbar = fig1.colorbar(CS)
A convex shape such as a triangle can be defined by the equations of the lines going through their vertices. In this case the equations are quite simple: X >= 0 is the zone right of the line through 0,0 and 0,2. Similar Y >= 0 and X + Y <= 2 are the two other zones. The triangle is the intersection of these 3 zones. Setting the corresponding Z values to NaN will create the empty triangle in the contour plot. import numpy as np import matplotlib.pyplot as plt delta = 0.025 x = y = np.arange(-3.0, 3.01, delta) X, Y = np.meshgrid(x, y) Z1 = np.exp(-X ** 2 - Y ** 2) Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2) Z = (Z1 - Z2) * 2 Z[(X >= 0) & (Y >= 0) & (X + Y <= 2)] = np.nan fig1, ax2 = plt.subplots() CS = ax2.contourf(X, Y, Z, 10, cmap=plt.cm.viridis, origin='lower', extend='both') ax2.set_title('Random Plot missing a triangle') ax2.set_xlabel('X Axis') ax2.set_ylabel('Y Axis') cbar = fig1.colorbar(CS) plt.show() PS: The equation of a line through two points x1,y1 and x2,y2 is (X - x1) * (y2 - y1) - (Y - y1) * (x2 - x1) == 0 So, a more general code could look like: def line_eq(X, Y, p1, p2): x1, y1 = p1 x2, y2 = p2 return (X - x1) * (y2 - y1) - (Y - y1) * (x2 - x1) >= 0 p = [(0, 0), (0, 2), (2, 0)] # clockwise ordering Z[line_eq(X, Y, p[0], p[1]) & line_eq(X, Y, p[1], p[2]) & line_eq(X, Y, p[2], p[0])] = np.nan Note that when the vertices are ordered counterclockwise, the equation should be <= 0 to grab the interior convex shape. Concave shapes can be created by taking the union (logical or) of several convex shapes: def line_eq(X, Y, p1, p2): x1, y1 = p1 x2, y2 = p2 return (X - x1) * (y2 - y1) - (Y - y1) * (x2 - x1) >= 0 def convex_eq(X, Y, p): mask = line_eq(X, Y, p[-1], p[0]) for p1, p2 in zip(p[:-1], p[1:]): mask &= line_eq(X, Y, p1, p2) return mask def multiple_convex_eq(X, Y, c): mask = convex_eq(X, Y, c[0]) for ci in c: mask |= convex_eq(X, Y, ci) return mask p = [(0, 2.5), (1.5, 1), (1, -2), (-1, -2), (-1.5, 1)] # pentagon, clockwise ordering five_trianggles = [[(0, 0), p1, p2] for p1, p2 in zip(p, (p + p)[2:])] Z[multiple_convex_eq(X, Y, five_trianggles)] = np.nan
sqrt-based filled ellipse pixel drawing function
I'm trying to make a function in Lua or VB based code to draw / plot a filled ellipse. I don't have much knowledge about this math and I can use some help. I Googled everything there is to Google about drawing ellipses with code but I can't find a good simple working example in VB or Lua for a filled one. On a previous post on this site I did get an answer about how to draw a normal ellipse but nothing came up for a filled one, that's why i make a new topic for a filled one. Here are a few websites I visited but I can't find a way to make a filled ellipse without redrawing already drawed pixels... https://sites.google.com/site/ruslancray/lab/projects/bresenhamscircleellipsedrawingalgorithm/bresenham-s-circle-ellipse-drawing-algorithm http://groups.csail.mit.edu/graphics/classes/6.837/F98/Lecture6/circle.html http://www.blitzbasic.com/codearcs/codearcs.php?code=2817 http://hackipedia.org/Algorithms/Graphics/pdf/A%20Fast%20Bresenham%20Type%20Algorithm%20For%20Drawing%20Ellipses%20by%20John%20Kennedy.pdf https://scratch.mit.edu/projects/49873666/ http://www.sourcecodesworld.com/source/show.asp?ScriptID=112 Here is the code I have for a normal ellipse (thanks to "Johnny Strings" for the VB version): function DrawEllipse(xc,yc,w,h) local w2 = w * w local h2 = h * h local fw2 = 4 * w2 local fh2 = 4 * h2 xc = xc + w yc = yc + h local x = 0 local y = h local s = 2 * h2 + w2 * (1 - h) while h2 * x <= w2 * y do dot(xc + x, yc + y) dot(xc - x, yc + y) dot(xc + x, yc - y) dot(xc - x, yc - y) redraw()inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) if s >= 0 then s = s + fw2 * (1 - y) y = y - 1 end s = s + h2 * ((4 * x) + 6) x = x + 1 end x = w y = 0 s = 2 * w2 + h2 * (1 - w) while w2 * y <= h2 * x do dot(xc + x, yc + y) dot(xc - x, yc + y) dot(xc + x, yc - y) dot(xc - x, yc - y) redraw()inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) if s >= 0 then s = s + fh2 * (1 - x) x = x - 1 end s = s + w2 * ((4 * y) + 6) y = y + 1 end end
Here's what I came up with for my CPU renderer in the past. It's very efficient and very simple too. It relies on the mathematical definition of the ellipse, so the ellipse is drawn centered at x,y and has the width and height defined from the center, not from the other side. The draw point function draws a pixel at the x by y point specified. local function drawaxisalignedellipse(x,y,w,h) --n Defines the bounds of the horizontal lines which fill the ellipse. local n=w local w2=w*w local h2=h*h --draws the center horizontal line. for i=x-w,x+w do drawpoint(i,y) end for j=1,h do --The current top and bottom rows. local ra,rb=y+j,y-j --This loop removes 1 from n until it is within the shape while w2*(h2-j*j)<h2*n*n and n~=0 do n=n-1 end --Draws horizontal line from -n to n across the ellipse for i=x-n,x+n do drawpoint(i,ra) drawpoint(i,rb) end end end
Draw an ellipse sqrt-based function
I'm trying to make a function in Lua or VB based code to draw / plot an ellipse and also a filled ellipse. I don't have much knowledge about this math and I can use some help. I googled everything there is to google about drawing ellipses with code but I can't find a good simple working example that i can code into my Lua / VB code. here are a few websites i visited but couldn't make the code work or couldn't convert the code to Lua or VB properly... https://sites.google.com/site/ruslancray/lab/projects/bresenhamscircleellipsedrawingalgorithm/bresenham-s-circle-ellipse-drawing-algorithm http://groups.csail.mit.edu/graphics/classes/6.837/F98/Lecture6/circle.html http://www.blitzbasic.com/codearcs/codearcs.php?code=2817 http://hackipedia.org/Algorithms/Graphics/pdf/A%20Fast%20Bresenham%20Type%20Algorithm%20For%20Drawing%20Ellipses%20by%20John%20Kennedy.pdf https://scratch.mit.edu/projects/49873666/ http://www.sourcecodesworld.com/source/show.asp?ScriptID=112 How do I draw an ellipse with arbitrary orientation pixel by pixel? Can anyone help me make code that can draw an ellipse and a filled ellipse? here is some code I tried to convert to Lua from here: https://gist.github.com/Wollw/3291916 this code has some problems (missing pixels) and I think it's not converted properly but I don't know how to do it otherwise. function plotEllipseRect(x0, y0, x1, y1) -- values of diameter a = math.abs(x1-x0) b = math.abs(y1-y0) b1 = 2.5 -- error increment dx = 4*(1-a)*b*b dy = 4*(b1+1)*a*a -- error of 1.step err = dx+dy+b1*a*a -- e2 = 0 if (x0 > x1) then -- if called with swapped points x0 = x1 x1 = x1 + a end if (y0 > y1) then -- .. exchange them y0 = y1 end -- starting pixel y0 = y0 + (b+1)/2 y1 = y0-b1 a = a * 8*a b1 = 8*b*b repeat dot(x1, y0) -- I. Quadrant dot(x0, y0) -- II. Quadrant dot(x0, y1) -- III. Quadrant dot(x1, y1) -- IV. Quadrant e2 = 2*err if (e2 <= dy) then -- y step y0 = y0 + 1 y1 = y1 - 1 dy = dy + a err = err + dy end if (e2 >= dx or 2*err > dy) then -- x step x0 = x0 + 1 x1 = x1 - 1 dx = dx + b1 err = err + dx end until (x0 >= x1) while (y0-y1 < b) do -- too early stop of flat ellipses a=1 dot(x0-1, y0) -- -> finish tip of ellipse y0 = y0 + 1 dot(x1+1, y0) dot(x0-1, y1) y1 = y1 - 1 dot(x1+1, y1) end end [EDIT:] I almost got it for the filled one! see the comments in this code below to know what the problem is... I use EGSL to test this Lua code: http://www.egsl.retrogamecoding.org//pages/downloads.php function DrawEllipse(xc,yc,w,h) local w2 = w * w local h2 = h * h local fw2 = 4 * w2 local fh2 = 4 * h2 xc = xc + w yc = yc + h local x = 0 local y = h local s = 2 * h2 + w2 * (1 - h) while h2 * x <= w2 * y do dot(xc + x, yc + y) dot(xc - x, yc + y) dot(xc + x, yc - y) dot(xc - x, yc - y) redraw() inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) --random color to see changes if s >= 0 then s = s + fw2 * (1 - y) y = y - 1 color(255,0,255) line(xc + x, yc + y, xc - x, yc + y) line(xc + x, yc - y, xc - x, yc - y) end s = s + h2 * ((4 * x) + 6) x = x + 1 end x = w y = 0 s = 2 * w2 + h2 * (1 - w) line(xc + x, yc + y, xc - x, yc + y) --to prevent the first line to be drawn twice redraw() inkey() s = s + w2 * ((4 * y) + 6) y = y + 1 while w2 * y < h2 * (x-2) do line(xc + x, yc + y, xc - x, yc + y) redraw() inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) line(xc + x, yc - y, xc - x, yc - y) redraw() inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) if s >= 0 then s = s + fh2 * (1 - x) x = x - 1 end s = s + w2 * ((4 * y) + 6) y = y + 1 end dot(xc + x, yc + y) dot(xc - x, yc + y) redraw() inkey() color(int(rnd()*255),int(rnd()*255),int(rnd()*255)) dot(xc + x, yc - y) dot(xc - x, yc - y) redraw() inkey() end openwindow (70,70,32,"Resize Window") color(255,255,0) DrawEllipse(10,10,20,20) --works perfect! inkey() cls() DrawEllipse(10,10,10,20) --problems with last 2 horizontal lines between the pixels! inkey() cls() DrawEllipse(10,10,20,10) --works perfect to! closewindow()
The following VB works for me, based on the first link provided; the only difference between mine here and the code at your link is I move xc and yc over, since you cannot can't have negative x or y values for the pixels in a bitmap. Public Shared Function DrawEllipse(ByVal xc As Integer, ByVal yc As Integer, ByVal w As Integer, ByVal h As Integer, ByVal doFill As Boolean) As Drawing.Bitmap Dim w2 As Integer = w * w Dim h2 As Integer = h * h Dim fw2 As Integer = 4 * w2 Dim fh2 As Integer = 4 * h2 // cheat by moving xc and yc so that we can handle quadrants xc = w yc = h Dim bm As New Drawing.Bitmap(w2, h2) // first half Dim x As Integer = 0 Dim y As Integer = h Dim s As Integer = 2 * h2 + w2 * (1 - h) While h2 * x <= w2 * y If doFill Then For i As Integer = -y To y bm.SetPixel(xc + x, yc + i, Drawing.Color.Red) bm.SetPixel(xc - x, yc + i, Drawing.Color.Red) Next Else bm.SetPixel(xc + x, yc + y, Drawing.Color.Red) bm.SetPixel(xc - x, yc + y, Drawing.Color.Red) bm.SetPixel(xc + x, yc - y, Drawing.Color.Red) bm.SetPixel(xc - x, yc - y, Drawing.Color.Red) End If If s >= 0 Then s += fw2 * (1 - y) y -= 1 End If s += h2 * ((4 * x) + 6) x += 1 End While // second half x = w y = 0 s = 2 * w2 + h2 * (1 - w) While w2 * y <= h2 * x If doFill Then For i As Integer = -x To x bm.SetPixel(xc + i, yc + y, Drawing.Color.Red) bm.SetPixel(xc + i, yc - y, Drawing.Color.Red) Next Else bm.SetPixel(xc + x, yc + y, Drawing.Color.Red) bm.SetPixel(xc - x, yc + y, Drawing.Color.Red) bm.SetPixel(xc + x, yc - y, Drawing.Color.Red) bm.SetPixel(xc - x, yc - y, Drawing.Color.Red) End If If s >= 0 Then s += fh2 * (1 - x) x -= 1 End If s += w2 * ((4 * y) + 6) y += 1 End While Return bm End Function (Aside: I used // instead of ' for the comments... just for readability here. If you copy to Visual Studio you'll have to fix that)
Ok, I managed to find a solution for the filled ellipse by checking if the pixel from the second half is gonna be drawn in the x-range of the first half of the ellipse. function drawellipse(xc, yc, w, h, dofill) --trouble with the size, 1 pixel to large on x and y to... w=w/2 --good solution for making it the right size? h=h/2 --good solution for making it the right size? local w2 = w * w local h2 = h * h local fw2 = 4 * w2 local fh2 = 4 * h2 -- cheat by moving xc and yc so that we can handle quadrants xc = xc + w yc = yc + h -- first half local x = 0 local y = h local s = 2 * h2 + w2 * (1 - h) while h2 * x <= w2 * y do if dofill then for i = -y , y do color(0,255,0) dot(xc + x, yc + i) dot(xc - x, yc + i) --redraw()inkey() end else color(255,0,255) dot(xc + x, yc + y) dot(xc - x, yc + y) dot(xc + x, yc - y) dot(xc - x, yc - y) --redraw()inkey() end if s >= 0 then s =s+ fw2 * (1 - y) y =y- 1 end s =s+ h2 * ((4 * x) + 6) x =x+ 1 end color(255,0,255) line(xc + x,0,xc - x,0) test1 = xc + x test2 = xc - x print(test1 .. '/' .. test2) redraw()inkey() -- second half x = w y = 0 s = 2 * w2 + h2 * (1 - w) while w2 * y <= h2 * x do if dofill then for i = -x , x do if not(xc + i > test2 and xc + i < test1) then color(255,255,0) dot(xc + i, yc + y) dot(xc + i, yc - y) redraw()inkey() end end else color(0,255,255) dot(xc + x, yc + y) dot(xc - x, yc + y) dot(xc + x, yc - y) dot(xc - x, yc - y) redraw()inkey() end if s >= 0 then s =s+ fh2 * (1 - x) x =x- 1 end s =s+ w2 * ((4 * y) + 6) y =y+ 1 end end
In vb.net you have both Graphics.DrawEllipse and Graphics.DrawArc. In Lua you may be able to use Cairo which I know has a arc function. If you where to make a ellipse in a GraphicsPath in .Net and where to reverse engineer how it is stored in memory, you would find out that it is stored as four bezier curves. I implemented my own graphics library in vb.net once, and that was how I did it. The best resource I found at the time where a implementation in Actionscript, that I unfortunately was unable to locate aswell as that graphics library I was talking about. TLDR; You should have a look at bezier curves.
A completely different, and very simple take on this, although the ellipse doesn't seem as "pretty" as the other algorithms; this just uses the mathematical definition of an ellipse and, looping over x calculates the y coordinate given x, w, and h. Public Shared Function DrawEllipse2(ByVal xc As Integer, ByVal yc As Integer, ByVal w As Integer, ByVal h As Integer, ByVal doFill As Boolean) As Drawing.Bitmap Dim bm As New Drawing.Bitmap(w * w, h * h) For x As Integer = xc - w To xc + w Dim y As Integer = CInt((Math.Sqrt(1 - ((x * x) / (w * w)))) * h) If doFill Then For j As Integer = -y To y bm.SetPixel(w + x, h + j, Drawing.Color.Red) Next Else bm.SetPixel(w + x, h + y, Drawing.Color.Red) bm.SetPixel(w + x, h - y, Drawing.Color.Red) End If Next Return bm End Function
Problems implementing Runge Kutta to solve a Damped Pendulum
I am a high school student working on a "home project" to animate a damped pendulum by solving differential equations using the Runge Kutta method. (The equations can be seen here: http://www.maths.tcd.ie/~smurray/Pendulumwriteup.pdf) I have been informed that in my code, my implementation of RK4 is not correct, and to be honest I have been struggling to understand it. The program is written in VB.net 2010. My code for solving the equations are as follows: Public Sub RK4Solve(ByRef The As Decimal, ByRef Ome As Decimal, ByRef h As Decimal) l1 = h * Ome k1 = h * f(The, Ome, h) l2 = h * (0.5 * l1) + Ome k2 = f((The + (0.5 * h * k1)), (Ome + (0.5 * h * l1)), (t + (0.5 * h))) l3 = h * (0.5 * l2) + Ome k3 = f((The + (0.5 * h * k2)), (Ome + (0.5 * h * l2)), (t + (0.5 * h))) l4 = h * l3 + Ome k4 = f((The + (h * k3)), (Ome + (h * l3)), (t + h)) 'Setting next step of variables The = The + (h / 6 * (l1 + (2 * l2) + (2 * l3) + l4)) Ome = Ome + (h / 6 * (k1 + (2 * k2) + (2 * k3) + k4)) t += h End Sub I am aware that I am multiplying each step by too many h's - I am just lost on what is happening. My full code: Public Class Form1 Dim l As Decimal = 1 'Length of rod (1m) Dim g As Decimal = 9.81 'Gravity Dim w As Decimal = 0 ' Angular Velocity Dim initheta As Decimal = -Math.PI / 2 'Initial Theta Dim theta As Decimal = -Math.PI / 2 'Theta (This one changes for the simulation) Dim t As Decimal = 0 'Current time of the simulation Dim h As Decimal = 0.01 'Time step Dim b As Decimal = Math.Sqrt(g / l) 'Constant used in the function for dw/dt Dim k As Decimal = 0 'Coefficient of Damping Dim initialx = l * Math.Sin(initheta) 'Initial Amplitude of the pendulum Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load End Sub 'Function for dw/dt Public Function f(ByRef the As Decimal, ByRef omega As Decimal, ByRef time As Decimal) Return ((-b ^ 2) * Math.Sin(the)) - (k * omega) + (initheta * Math.Cos(omega * time)) End Function Dim k1, k2, k3, k4, l1, l2, l3, l4 As Decimal 'Initialising RK4 variables Public Sub RK4Solve(ByRef The As Decimal, ByRef Ome As Decimal, ByRef h As Decimal) l1 = h * Ome k1 = h * f(The, Ome, h) l2 = h * (0.5 * l1) + Ome k2 = f((The + (0.5 * h * k1)), (Ome + (0.5 * h * l1)), (t + (0.5 * h))) l3 = h * (0.5 * l2) + Ome k3 = f((The + (0.5 * h * k2)), (Ome + (0.5 * h * l2)), (t + (0.5 * h))) l4 = h * l3 + Ome k4 = f((The + (h * k3)), (Ome + (h * l3)), (t + h)) 'Setting next step of variables The = The + (h / 6 * (l1 + (2 * l2) + (2 * l3) + l4)) Ome = Ome + (h / 6 * (k1 + (2 * k2) + (2 * k3) + k4)) t += h End Sub 'Timer ticking every 0.1s 'Time step is 0.01s to increase accuracy of results for testing Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick ComboBox1.Items.Add(theta) 'Adding theta to a drop down box to test data RK4Solve(theta, w, h) End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Timer1.Enabled = False End Sub End Class I have been trying to solve this for a while now, and I'm on my last legs so I am resorting to asking for help. Thanks to anyone that can!
It helps if you separate the differential equations from the RK4 implementation. Then you can implement RK4 as in the documentation k1 = f1( y1, y2, x) l1 = f2( y1, y2, x) k2 = f1( y1 + 0.5*h*k1, y2 + 0.5*h*l1, x + 0.5*h) l2 = f2( y1 + 0.5*h*k1, y2 + 0.5*h*l1, x + 0.5*h) k3 = f1( y1 + 0.5*h*k2, y2 + 0.5*h*l2, x + 0.5*h) l3 = f2( y1 + 0.5*h*k2, y2 + 0.5*h*l2, x + 0.5*h) k4 = f1( y1 + h*k3, y2 + h*l3, x + h) l4 = f2( y1 + h*k3, y2 + h*l3, x + h) y1 = y1 + h/6*(k1+2*(k2+k3)+k4) y2 = y2 + h/6*(l1+2*(l2+l3)+l4) x = x + h Not only does it help to avoid the duplication of the multiplication by h, but also the duplication of the addition of the base point values Ome and The.
Excel VBA compile error: Expected Sub, Function or Property
I am getting a compile error in Excel VBA which says Expected Sub, Function or Property. The function I am using is given below which is trying to copy the rate function in Excel. Thanks for your help. Function rate_m(nper As Double, pmt As Double, pv As Double, fv As Double, types As Double, guess As Double) As Variant Dim y, y0, y1, x0, x1, f, i As Double Dim FINANCIAL_MAX_ITERATIONS As Double Dim FINANCIAL_PRECISION As Double If IsNull(guess) Then guess = 0.01 If IsNull(fv) Then fv = 0 If IsNull(types) Then types = 0 FINANCIAL_MAX_ITERATIONS = 128 'Bet accuracy with 128 FINANCIAL_PRECISION = 0.0000001 '1.0e-8 y , y0, y1, x0, x1, f, i = 0 rate_m = guess If Abs(rate_m) < FINANCIAL_PRECISION Then y = pv * (1 + nper * rate_m) + pmt * (1 + rate_m * types) * nper + fv Else f = Exp(nper * Log(1 + rate_m)) y = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv y0 = pv + pmt * nper + fv y1 = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv End If 'find root by Newton secant method i , x0 = 0 x1 = rate_m While Abs(y0 - y1) > FINANCIAL_PRECISION & i < FINANCIAL_MAX_ITERATIONS rate_m = (y1 * x0 - y0 * x1) / (y1 - y0) x0 = x1 x1 = rate_m If Abs(rate_m) < FINANCIAL_PRECISION Then y = pv * (1 + nper * rate_m) + pmt * (1 + rate_m * types) * nper + fv Else f = Exp(nper * Log(1 + rate_m)) y = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv End If y0 = y1 y1 = y i = i + 1 Wend End Function
A couple things... First, you have to assign each variable individually...like this: y = 0 y0 = 0 y1 = 0 x0 = 0 x1 = 0 f = 0 i = 0 Second, you probably want to DIM your variables all as Double. Unfortunately, this line: Dim y, y0, y1, x0, x1, f, i As Double Only declares i as a Double, all the others will be a Variant. You need to declare each one individually, like this: Dim y As Double Dim y0 As Double Dim y1 As Double Dim x0 As Double Dim x1 As Double Dim f As Double Dim i As Double
Every IF ends with a End If (unless in a single line) and While...loop. You might want to take a look at VBA's syntax: http://msdn.microsoft.com/en-us/library/office/ee814737(v=office.14).aspx EDIT: You have to declare variable individually, instead of: y , y0, y1, x0, x1, f, i = 0 you could do: y = 0 y0 = 0 y1 = 0 x0 = 0 x1 = 0 f = 0 i = 0