Kolyunya
Kolyunya

Reputation: 6240

How to return the point of lines crossing? Lines don't always intersect

Let's say we want to make a function which calculates the intersection point of two lines. The intersection point is't always defined or unique. How to reflect that in the function's signature?

I have come up with these options:

  1. bool getIntersectionPoint ( Line& a, Line& b , Point& result );

    Returns false if lines are parallel. Otherwise returns true and writes result to the variable.

  2. Point getIntersectionPoint ( Line& a, Line& b );

    Throws an exception if lines are parallel.

[update]
If we make 2 functions bool doLinesIntersect(const Line&, const Line&); and Point twoLinesIntersection(const Line&, const Line&); the second one can still be called after the first returns false.

Upvotes: 6

Views: 519

Answers (7)

kassak
kassak

Reputation: 4194

IMHO, line intersection yields object, that's why it would be honest to have

boost::variant<Empty, Point, Line> intersect(Line const & l1, Line const & l2)

and helper functions, like

boost::optional<Point> getIntersectionPoint(Line const & l1, Line const & l2)

bool isParallel(Line const & l1, Line const & l2)

Edit: If you do not want to use boost library you can easily create simple analogues:

struct intersection_result_t
{
  enum isec_t
  {
    isec_empty, isec_point, isec_line
  }

  intersection_result_t()
    : type_(isec_empty)
  {
    new (storage_) Empty();
  }

  intersection_result_t(Empty const & e)
    : type_(isec_empty)
  {
    new (storage_) Empty(e);
  }
  intersection_result_t(Point const & p)
    : type_(isec_point)
  {
    new (storage_) Point(p);
  }
...
  intersection_result_t(intersection_result_t & ir)
    : type_(ir.type_)
  {
    switch(ir.type_)
    {
      case isec_empty:
        new (storage_) Empty(*static_cast<Empty*>(ir.storage_));
      case ....
    }
  }
private:
  void destroy()
  {
    switch(type_)
    {
      case isec_empty:
        operator delete (static_cast<Empty*>(storage_), storage_);
      case ....
    }
  }
private:
  char storage_[MAX(sizeof(Empty), sizeof(Point), sizeof(Line))];
  isec_t type_;
};

etc, etc some more switches needed. Or you can use templates. For optional just use initialized_ instead of type_ to track construction state.

Upvotes: 4

Cassio Neri
Cassio Neri

Reputation: 20533

As suggested by ulidtko, it would be good to return an object that "may be a Point". In C++ you can use boost::optional

boost::optional<Point> getIntersectionPoint(const Line& a, const Line& b) {
    // ...
    if (there_is_zero_or_inifinty_points_of_intersection)
        return boost::optional<Point>();
    else
        return boost::optional<Point>(the_point_of_intersection);
}

You can think of boost::optional<Point> as if it was a Point*. In particular, a client can query if the intersection returned is a proper point or not in this way:

boost::optional<Point> point = getIntersectionPoint(a, b);
if (point)
    // point "points to" a proper Point which can be retrieved as *point
else
    // point is "NULL", that is, there's no unique point of intersection

Funnily enough, the motivating example of boost::optional is also a geometric problem. This is not a coincidence since the boost::optional author, I believe, writes geometric software. ;-)

It's worth mentioning that there's a proposal to include optional to the STL in the next revision of the C++ standard.

Upvotes: 3

qPCR4vir
qPCR4vir

Reputation: 3571

Without given the context people will discuss without end.

Supouse you want to use the function inside some

fillWithColor(color c, set_of lines& figure);

and somehow you use getLinesIntersection to do that. If you need to check each call not only will your code a mess, but you don’t know what to do with the error. Simple use the function and let the caller catch the exception.

In other context you can implement:

bool doLinesIntersect(const Line&, const Line2&, Point &p);
Point getLinesIntersection(const Line&, const Line2&)
{
   Point p;
   If (! doLinesIntersect(Line, Line2,p) throw …;
   return p;
}

Both aproach are very valid !!!

Upvotes: -1

ulidtko
ulidtko

Reputation: 15634

From the abstraction (API) perspective, you have two unrelated functions:

bool doLinesIntersect(const Line&, const Line&);

and

Point twoLinesIntersection(const Line&, const Line&);

The second function must assume that the lines do actually intersect (and aren't collinear). If you don't trust your callers, you may want to throw an exception indicating that preconditions aren't met.

Upvotes: 0

ulidtko
ulidtko

Reputation: 15634

This question is a very good motivation for easier sum types in C++.

In a language like Haskell your function would've had the following signature:

getIntersectionPoint :: Line -> Line -> Maybe Point

where Maybe Point (the return type of the function) essentially means a type which can have two values: Nothing or Just p, where p is a Point.

Availability of such easy sum types actually would've made the question unnessesary at all, because all approaches would've merge into a single one.


Edit: this answer demonstrates neatly that Boost offers easy sum types facilities. There are boost::optional and boost::variant. Sweet.

Upvotes: 0

Pete
Pete

Reputation: 4812

Your second function should probably not return a Point& but a Point value (who owns it?)

Alternatively, there is a 3rd option:

Point getIntersectionPoint ( Line& a, Line& b, bool* ok );

If you provide a NULL pointer for 'ok', throw if there is no intersection otherwise return false in the value of 'ok'.

I would suggest that for a function like this, it is better to avoid exceptions entirely. A non-intersection is not really all that exceptional and exceptions should really be reserved fro stuff that is unexpected. You can expect non-intersecting lines.

Use the version that returns bool, or the version with a bool argument but don't throw.

EDIT A fourth option that is often used:

std::pair<bool, Point> getIntersectionPoint ( Line& a, Line& b );

Upvotes: 0

john
john

Reputation: 8027

Parallel lines are not an error, or unexpected. Therefore throwing an exception is not appropriate.

BTW this is preferable as a function signature.

bool getIntersectionPoint(const Line& a, const Line& b, Point& result);

Specifying const makes it clear that the function does not modify it's first two arguments, and also allows you to call the function with temporaries.

Upvotes: 0

Related Questions