5
\$\begingroup\$

I have a function that takes a falling car's location & velocity as well as the world gravity. The world has a floor, ceiling, and walls that are in fixed positions. The car's x & y velocity can't change once it's airborne, but gravity does force the car down. I'm trying to find exactly which plane the car will land on first.

This is my current solution, which actually works just fine:

#include <math.h>

// Vector stuff

typedef struct vector
{
    double x;
    double y;
    double z;
} Vector;

static inline Vector add(Vector vec1, Vector vec2)
{
    return (Vector){vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z};
}

static inline Vector multiply(Vector vec1, Vector vec2)
{
    return (Vector){vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z};
}

static inline double dot(Vector vec1, Vector vec2)
{
    return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
}

static inline double magnitude(Vector vec)
{
    return sqrt(dot(vec, vec));
}

static inline Vector double_to_vector(double num)
{
    return (Vector){num, num, num};
}

// find the falling car's landing plane

int find_landing_plane(Vector car_location, Vector *car_velocity, double *gravity)
{
    Vector l = car_location;
    Vector v = *car_velocity;
    Vector V_simulation_dt = double_to_vector(simulation_dt);
    double g = *gravity * simulation_dt;

    if (fabs(l.y) >= 5120)
        return 5; // this is a special exception

    while (1)
    {
        if (magnitude(v) < 2300) // 2300 = terminal velocity
            v.z = v.z + g;
        l = add(l, multiply(v, V_simulation_dt));

        // there must be a better way to do this...
        if (l.x >= 4080)
            return 0; // wall
        if (l.x <= -4080)
            return 1; // wall
        if (l.y >= 5110)
            return 2; // wall
        if (l.y <= -5110)
            return 3; // wall
        if (l.z >= 2030)
            return 4; // ceiling
        if (l.z <= 20)
            return 5; // floor
    }
}

I'm new to C, but I do have a few years of programming experience... SURELY there's a better way to do this? I was thinking perhaps something to do with the trajectory formula, or just a way to make the if statements nicer to look at would also be great.

\$\endgroup\$
0
5
\$\begingroup\$

Don't hardcode values

There are lots of magic values that you are using in your code, from the terminal velocity, the wall coordinates to the numbering of the walls. Try to give everything a name: declare constants for the coordinates and terminal velocity, and create an enum for the six faces of the enclosing box:

enum Face {
    FACE_RIGHT,
    FACE_LEFT,
    FACE_BACK,
    FACE_FRONT,
    FACE_TOP,
    FACE_BOTTOM,
};

static const double terminal_velocity = 2300;
static const double right_wall_position = 4080;
...

It's a little bit more typing, but there are several benefits. In particular, it makes the code more self-documenting. Consider the return type of find_landing_plane():

enum Face find_landing_plane(...) {
    ...
    if (l.x > right_wall_position)
        return FACE_RIGHT;
    ...
}

You also don't need to add a comment to this line anymore:

if (magnitude(v) < terminal_velocity)

Consider using arrays to avoid repetition

You could get rid of the repeated if-statements by treating the vector as an array, and also having an array to encode the positions of the planes. It would look like this:

static const double plane_positions[6] = {4080, -4080, 5110, -5110, 2030, 20};
...
enum Face find_landing_plane(Vector car_location, Vector *car_velocity, double *gravity)
{
    ...
    while (1)
    {
        // update l
        ...
        
        double *l_vec = &l.x;

        for (int i = 0; i < 6; i++) {
            if (i % 2 == 0 ? l_vec[i / 2] >= plane_positions[i] : l_vec[i / 2] <= plane_positions[i])
                return i;
        }
    }
}   

However, I'm not sure it is all that better, as it uses a complex expression and no longer uses the enum values by name, making it harder to understand and easier to make mistakes.

Solve the problem analytically

Finding the plane the car hits by updating the position in a loop is inefficient. The problem can be solved analytically. The trajectory the car follows is a parabola up to the point the terminal velocity is reached. You can solve for time the parabola intersects each of the the six planes, and also solve for the time the terminal velocity is reached. If the earliest time found that is not in the past is a plane, you know that plane was hit first. If the earliest time is when the terminal velocity is reached, you then now that the rest of the trajectory is a straight line, so you can then do the even easier step of solving for the intersection of a straight line with six planes.

This is more code to write, although here you could also make an array with the parameters describing each of the six planes, and use a for-loop to check for the intersection with each of them. The advantage is that it should be much more efficient and much more precise (consider that you have a bias in your algorithm when the car hits very close to a corner).

Add a better description of the special exception

Why is fabs(l.y) > 5120 special? You commented that it is an exception, but that still doesn't tell me why. Why is it 5120 (consider making a constant for that as well), and why do you need to handle this case at all, when it looks like the rest of the code would handle that fine?

\$\endgroup\$
6
  • \$\begingroup\$ fabs(l.y) > 5120 is special because the field isn't a perfect cuboid, and there are two, small extra shapes extending from the main ones. I'm calling them 'shapes' bc they're a little funky, but they have a flat floor so I just say if the car is in one of them then recover on the floor \$\endgroup\$
    – VirxEC
    Dec 4 '20 at 20:00
  • \$\begingroup\$ Also, are there any performance benefits for not using hardcoded values? Every microsecond counts, due to the nature of the program that this is apart of \$\endgroup\$
    – VirxEC
    Dec 4 '20 at 20:06
  • \$\begingroup\$ You say You can solve for time the parabola intersects each of the the six planes, and I'm probably going to do that, but... what's the name of the formula to find that? Thanks -_- \$\endgroup\$
    – VirxEC
    Dec 4 '20 at 20:42
  • \$\begingroup\$ Would I just use a 3d quadratic equation or something? \$\endgroup\$
    – VirxEC
    Dec 4 '20 at 20:43
  • 2
    \$\begingroup\$ You probably could, but since the planes are axis aligned, I would just consider the x, y and z components separately. That way, it just becomes 3 2D problems (x,t, y,t and z,t). \$\endgroup\$
    – G. Sliepen
    Dec 4 '20 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.