Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Line handling improvements. #92

Open
4 of 5 tasks
HexDecimal opened this issue Apr 16, 2021 · 6 comments
Open
4 of 5 tasks

Line handling improvements. #92

HexDecimal opened this issue Apr 16, 2021 · 6 comments

Comments

@HexDecimal
Copy link
Collaborator

HexDecimal commented Apr 16, 2021

Copied from this comment: #91 (comment)

Since line drawing is a pretty low-level task most of the code should be moved inline, even in C using C99 inline functions. I haven't gotten used to how to implement these correctly.

For the callback-based functions. The C callbacks need an additional userdata parameter to be useful. C++ should support lambdas as callbacks.

C should have a function that outputs a full array of line indexes to a buffer. C++ might use this function to return a std::vector of line coordinates.

C++ should have a dynamic iterator supporting ranged-based for loops to iterate over line coordinates without a heap buffer.

  • C array output.
  • C++ forward iterator with range support.
  • C++ iterator with custom ranges.
  • C++ bidirectional iterator.
  • C++ arbitrary iteration.
@HexDecimal
Copy link
Collaborator Author

HexDecimal commented Apr 16, 2021

Ideally C++ should have range and structured binding support, so that you can iterate over a line like this:

for (auto&& [x, y] : tcod::bresenham({start_x, start_y}, {end_x, end_y})) {
  // ...
}

In C there should be an additional option to output the indexes to an array output:

int length = TCOD_bresenham(start_x, start_y, end_x, end_y, 0, NULL);  // TCOD_bresenham(x, y, x2, y2, n, out)
int* indexes = malloc(sizeof(*indexes) * length * 2);
TCOD_bresenham(0, 0, 4, 4, length, indexes);

@sensualcoder
Copy link
Contributor

sensualcoder commented Apr 16, 2021

For the C++ portion, I could add an InputIterator definition to the BresenhamLine class and create a new free function based on the callback one that doesn't take a callback, then the function can return that iterator. The iterator get method can then return the [x, y] structured bindings.

The function definition can be something like inline BresenhamLine::iterator bresenham(std::pair<int, int> from, std::pair<int, int> to);

Edit: Actually, just defining a constructor that takes two std::pair<int, int> and the iterator would work, then you can just do:

for(auto&& [x, y] : tcod::BresenhamLine({0, 0}, {10, 10}))
{
    // do work
}

@HexDecimal
Copy link
Collaborator Author

I think I'll always prefer std::array<int, 2> over std::tuple<int, int> or std::pair<int, int>. std::pair in particular has implementation issues which can mess up compiler optimizations when used (not sure if this is still the case.)

@HexDecimal
Copy link
Collaborator Author

I've made significant changes to the BresenhamLine class. It no longer uses the C functions and can even iterate over a Bresenham line in reverse. It supports clipping the endpoints. It's slightly faster than calling the C function.

Later I'll probably change it so that the iterator functionality is part of the main class instead of a separate internal class. I think the hard parts are already done.

@sensualcoder
Copy link
Contributor

That's awesome, I never would have thought about decomposing the problem like that. Maybe I should brush up on my math a little, since even inverting the Bresenham algorithm seemed a little beyond me.

@HexDecimal
Copy link
Collaborator Author

I quick look at the Wikipedia article on Bresenham explains how the integer version of Bresenham is implemented. It's really just a fancy way of handling a division operation without using real division by keeping track of the fractional remainder manually (the error variable.)

A lot of the algorithm was for handling edge cases with octants, I could represent octants as transform matrices instead of having to implement those edge cases in the algorithm itself, which means I only have to do one octant (the low slope) and that keeps the real implementation simple. At that point it's easy to see how to reverse Bresenham's algorithm. The C++ compiler optimizes out the complexity of the transform matrix since it's generated from a pure inline function. The way the State object is kept in sync with the iterator is also optimized out. So in the end this runs a little faster than the original C call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants