Skip to content

Core: Fix BitMap RDP algorithm - #94602

Open
Sinowa-Programming wants to merge 24 commits into
godotengine:masterfrom
Sinowa-Programming:RDP_Self_Intersection-Fix
Open

Core: Fix BitMap RDP algorithm#94602
Sinowa-Programming wants to merge 24 commits into
godotengine:masterfrom
Sinowa-Programming:RDP_Self_Intersection-Fix

Conversation

@Sinowa-Programming

Copy link
Copy Markdown

@clayjohn
clayjohn requested a review from AThousandShips July 22, 2024 05:20
@clayjohn clayjohn added this to the 4.x milestone Jul 22, 2024
@AThousandShips

Copy link
Copy Markdown
Member

I'll take a look as soon as I'm able, really excited to see a potential fix to this!

@akien-mga akien-mga changed the title Core: Fix RDP algorithm Core: Fix BitMap RDP algorithm Jul 22, 2024
@AThousandShips

Copy link
Copy Markdown
Member

Will do some testing with the MRP I made for a previous bug involving this, very useful for these purposes, uploading here for new use:
BitPR2.zip

@AThousandShips

AThousandShips commented Jul 22, 2024

Copy link
Copy Markdown
Member

It seems the assumption that the intersecting segment must always be at the end is not valid, it fails with cases like this one:
image

Some more examples:
image

So while the improvements to the general algorithm are great this does not solve the self intersection problem at least not reliably

@Sinowa-Programming

Copy link
Copy Markdown
Author

Thanks for pointing that out. I am going to continue working on implementing the star RDP algorithm then.

@AThousandShips

Copy link
Copy Markdown
Member

Having worked on trying to make it work myself I know it's a challenging task, might be worth it to split this into two PRs, one doing the basic fixes to the behavior and one to try and fix the self-intersections

@Sinowa-Programming

Sinowa-Programming commented Jul 24, 2024

Copy link
Copy Markdown
Author

I agree. I also believe the ending self-intersection check should be kept as it covers the edge cases from #69725. I'll do another pull request when the star RDP is done.

@AThousandShips

Copy link
Copy Markdown
Member

Do you mean #69725?

@Sinowa-Programming

Copy link
Copy Markdown
Author

yeah

@Sinowa-Programming

Copy link
Copy Markdown
Author

I finally finished the star RDP and am including it in this pull request. AFAIK it handles all self-intersection cases.

@Sinowa-Programming
Sinowa-Programming force-pushed the RDP_Self_Intersection-Fix branch from b3af604 to 34459a6 Compare August 12, 2024 03:04
@Sinowa-Programming

Sinowa-Programming commented Aug 13, 2024

Copy link
Copy Markdown
Author

While investigating why the check failed I stumbled upon a bug within the algorithm implementation. While I fixed it, I still need to do more testing. :(

@AThousandShips

AThousandShips commented Aug 13, 2024

Copy link
Copy Markdown
Member

This is something to do extensive testing on indeed, and a big question is how to approach performance, and we need to measure the performance here and weigh the benefits and drawbacks

Not familiar with this specific algorithm and haven't studied the code but it should be at least O(N log N) compared with the standard RDP of O(N), but it should at least avoid the more expensive brute force check cost of O(N^2)
Mixed up the performance, unsure what each method has as complexity, worst case O(NM) where N is the number of input vertices and M the number of output segments. Compared with this algorithm which should have O(NM) where N is the same but M is the number of star shaped regions, that would depend heavily on the data and probably in a bit more of a input dependent way

But that'd be a matter of performance testing on complex cases like large images to confirm that it is indeed not very slow

I would suggest an optional argument defaulting to false which enables the star algorithm, and then always using the algorithm for importing images and similar, but making it opt-in for general users, as most input doesn't need this, so you have a more expensive algorithm without necessarily needing one

@Sinowa-Programming
Sinowa-Programming force-pushed the RDP_Self_Intersection-Fix branch from 34459a6 to a867b7b Compare August 20, 2024 03:21
@Sinowa-Programming
Sinowa-Programming requested review from a team as code owners August 20, 2024 03:22
@Sinowa-Programming
Sinowa-Programming force-pushed the RDP_Self_Intersection-Fix branch from 73ed862 to 6c7ec19 Compare August 20, 2024 03:30
@Sinowa-Programming

Copy link
Copy Markdown
Author

Sorry for the slow update, I haven't had much time to work on this last week. The bug has been fixed, but I am unsure about how to go about making the star RDP optional. If I add a bool, it will force me to always add the default epsilon value every time I call the function and want to use the flag.
EX(From Resource importer): bit_map->clip_opaque_to_polygons(Rect2(Vector2(), image->get_size()), 2.0, true);
If this is fine I will go ahead and update the code with it.
Also for use in the sprite2d editor, should it just be a toggle button to enable it?

@Sinowa-Programming
Sinowa-Programming requested a review from a team August 21, 2024 05:10
@fire

fire commented Aug 21, 2024

Copy link
Copy Markdown
Member

What is the harm of leaving star RDP on for all cases?

@AThousandShips

AThousandShips commented Aug 21, 2024

Copy link
Copy Markdown
Member

The cost, since most inputs don't need this, but that would depend on performance measurements as mentioned above

@Sinowa-Programming
Sinowa-Programming force-pushed the RDP_Self_Intersection-Fix branch from 1693d01 to 7fbc886 Compare August 22, 2024 05:19
@Sinowa-Programming
Sinowa-Programming requested a review from a team as a code owner August 22, 2024 05:19
@Sinowa-Programming
Sinowa-Programming force-pushed the RDP_Self_Intersection-Fix branch 3 times, most recently from 5b90145 to 4f1f430 Compare August 26, 2024 00:38
Comment thread scene/resources/bit_map.cpp Outdated
}

static bool does_obb_collide_with_line(const Vector<Vector2> &obb, const Vector<Vector2> &line) {
int obb_size = 4; // obb is a rectangle

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can generate_obb_from_polyline() return 2 points for a straight line? Is this always 4?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it is always 4 points. The function is only called in does_polyline_obbs_collide, which is only called in recursive_obb_collision_check, that has a base_case gate that prevents two lines from entering the function.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case then a DEV_ASSERT might be worth it here, to check the assumptions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the colinear case, with a three point subchain (0,0), (5,0), (10,0). Can that be a problem?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it is always 4 points. The function is only called in does_polyline_obbs_collide, which is only called in recursive_obb_collision_check, that has a base_case gate that prevents two lines from entering the function.

This depends on the caching mechanism and convex hull being bug free, I'm not sure we should rely on this. Can we use obb.size() dynamically inside does_obb_collide_with_line()?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would be a problem as does_polyline_obbs_collide would call generate_obb_from_polyline, which would crash once the generated convex hull is empty instead of creating the required four points.

A situation where this could happen is after an intersection is fixed and a point is added colinearly between a pair of points.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh srry, your response never loaded until I refreshed, yeah it's the right call to just use obb.size().

Comment thread scene/resources/bit_map.cpp Outdated
while (iter_node->next()) {
iter_node = iter_node->next();
float ndx = iter_node->get()[0] - iter_node->prev()->get()[0];
if (dx * ndx < 0) { // If they are not the same direction

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (dx * ndx < 0) { // If they are not the same direction
if (dx * ndx <= 0) { // If they are not the same direction

@lawnjelly

Copy link
Copy Markdown
Member

Is using a hashmap with first and last point as the key sound? Can there be subchains that have the same start and end but be different within?

result, mapped_result, mono_chain_lst, pl);
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this end up inserting the split point into ch1_list instead of ch2_list? Can we guard against this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe it should as on line 971, I have a if statement that switches between ch1_list and ch2_list. Even if the chains are next to each other, find_furthest_perp_point_from_edge will only return a point that was in-between the ch1_list start and end points. If there are no points are in-between, the code will skip the insertion and continue recursing down the rest of the chain.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that if there is no new point added, there is no need to recurse down the chain again. Thanks for the heads up.

Comment thread scene/resources/bit_map.cpp Outdated
new_chain_end_nodes.push_back(chain_node_to_split); // low -> low + 1
} else {
// There are no points that can be added to fix the chain
new_chain_end_nodes.push_back(chain_node_to_split);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new_chain_end_nodes.push_back(chain_node_to_split);
new_chain_end_nodes.push_back(nullptr);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still needs to be added to the list because col_chain_start and col_chain_end are based off of the index. Chain 1 has index 0 and 1, Chain 2 has index 2 and 3.

Comment thread scene/resources/bit_map.cpp Outdated

// Returns the index of the point with the furthest perpendicular distance from the edge in the given range
static int find_furthest_perp_point_from_edge(const PackedVector2Array &pl, const int start, const int end) {
int max_dist = -1;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int max_dist = -1;
float max_dist = -1;

It should be a float to correctly store the squared distance.

@lawnjelly

Copy link
Copy Markdown
Member

Overall: This is really difficult to implement, so don't be discouraged, as it would be super useful.

I would add more defensive programming over assumptions, use DEV_ASSERT / DEV_CHECK liberally, and if possible just have the code deal with assumptions being broken. (e.g. the 2 points in does_obb_collide_with_line).

Test cases to cover

  • Zigzag (0, 0) -> (0, 10) -> (10, 10) -> (10, 20) -> (20, 20)
  • Vertical Start Segment (0,0) -> (0,10) -> (10,10) -> (10,20)→(20,20)
  • Colinear (0,0) -> (5,0) -> (10,0)

@AThousandShips

Copy link
Copy Markdown
Member

I'll make a new pass of this soon! A lot of things got in the way earlier

@Sinowa-Programming

Copy link
Copy Markdown
Author

Is using a hashmap with first and last point as the key sound? Can there be subchains that have the same start and end but be different within?

There is only one instance of the chain that is actually edited, so if they have the same start the end, the internals should be the same. That is why I went with a list.

I also just realized the newly created sub-chains would not have their OBB recalculated to fit the newly added point. This may cause an issue where the newly added point is a spike that creates a new intersection against a chain that wasn't previously colliding.

@Sinowa-Programming

Copy link
Copy Markdown
Author

Thanks for spending the time to review my code! I'll work on it to hopefully get it merged soon.

@AThousandShips

Copy link
Copy Markdown
Member

At a glance this approach looks somewhat familiar to my own attempts to resolve this so it looks like a theoretically sound approach, but will try to dig into the code and theory

@HolonProduction
HolonProduction removed request for a team July 20, 2026 09:18
@Nintorch
Nintorch removed the request for review from a team July 20, 2026 09:23
@mattmeyer

Copy link
Copy Markdown

Thank you all so much for the work on this! I'm excited to see this fix/feature come together.

@AThousandShips

Copy link
Copy Markdown
Member

Building this PR and will try to do some validation on the cases that was an issue in the bug report, and I'll try to do a pass over the code this week as well, time permitting

@AThousandShips

AThousandShips commented Jul 22, 2026

Copy link
Copy Markdown
Member

Doing some basic testing and have found some cases where I can cause a crash in the PR version, will upload an example for it to replicate

image

For ease of replication here's the data itself:

{ "size": Vector2i(36, 20), "data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 6, 0, 0, 32, 48, 0, 0, 0, 130, 1, 0, 0, 32, 12, 0, 0, 0, 102, 0, 0, 0, 192, 195, 1, 0, 0, 4, 6, 0, 0, 192, 48, 0, 0, 0, 136, 1, 0, 0, 128, 13, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }

It crashes with the following error and trace:

Details

ERROR: FATAL: Index p_index = 2 is out of bounds (size() = 2).
   at: get (./core/templates/cowdata.h:200)
   GDScript backtrace (most recent call first):
       [0] _do_plot (res://main.tscn::GDScript_3tay1:26)
       [1] _input (res://main.tscn::GDScript_3tay1:62)

================================================================
handle_crash: Program crashed with signal 4
Engine version: Godot Engine v4.8.dev.custom_build (41b9467dac3914dc94fb76128a4c0ec76edafe5f)
Dumping the backtrace. Please include this when reporting the bug on: https://github.com/godotengine/godot/issues
Load address: 400000

[1] 7fe430cb2290 (libc.so.6+1a290) - /lib64/libc.so.6(+0x1a290) [0x7fe430cb2290]
[2] 45d462 (main+5d462) - CowData<Vector2>::get(long) const at ./core/templates/cowdata.h:200
[3] 4581e5 (main+581e5) - Vector<Vector2>::operator[](long) const at ./core/templates/vector.h:145
[4] 4e48c53 (main+4a48c53) - does_obb_collide_with_line(Vector<Vector2> const&, Vector<Vector2> const&) at ./scene/resources/bit_map.cpp:604
[5] 4e4902e (main+4a4902e) - does_polyline_obbs_collide(Vector<Vector2> const&, Vector<Vector2> const&, HashMap<OBBCacheKey, Vector<Vector2>, OBBCacheKeyHasher, OBBCacheKeyComparator, DefaultTypedAllocator<HashMapElement<OBBCacheKey, Vector<Vector2> > > >&, OBBCacheKey, OBBCacheKey, bool, bool) at ./scene/resources/bit_map.cpp:687
[6] 4e49fe9 (main+4a49fe9) - recursive_obb_collision_check(List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, Vector<ChainBoxData const*>&, HashMap<ChainBoxData const*, Vector<ChainBoxData const*>, HashMapHasherDefault, HashMapComparatorDefault<ChainBoxData const*, void>, DefaultTypedAllocator<HashMapElement<ChainBoxData const*, Vector<ChainBoxData const*> > > >&, HashMap<OBBCacheKey, Vector<Vector2>, OBBCacheKeyHasher, OBBCacheKeyComparator, DefaultTypedAllocator<HashMapElement<OBBCacheKey, Vector<Vector2> > > >&, List<Vector2, DefaultAllocator>&, Vector<long>&, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>&, Vector<Vector2> const&) at ./scene/resources/bit_map.cpp:904
[7] 4e4a84a (main+4a4a84a) - iterative_refinement(List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, Vector<ChainBoxData const*>&, HashMap<ChainBoxData const*, Vector<ChainBoxData const*>, HashMapHasherDefault, HashMapComparatorDefault<ChainBoxData const*, void>, DefaultTypedAllocator<HashMapElement<ChainBoxData const*, Vector<ChainBoxData const*> > > >&, HashMap<OBBCacheKey, Vector<Vector2>, OBBCacheKeyHasher, OBBCacheKeyComparator, DefaultTypedAllocator<HashMapElement<OBBCacheKey, Vector<Vector2> > > >&, List<Vector2, DefaultAllocator>&, Vector<long>&, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>&, Vector<Vector2> const&) at ./scene/resources/bit_map.cpp:1022
[8] 4e49f53 (main+4a49f53) - recursive_obb_collision_check(List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, List<Vector2, DefaultAllocator>::Element*, Vector<ChainBoxData const*>&, HashMap<ChainBoxData const*, Vector<ChainBoxData const*>, HashMapHasherDefault, HashMapComparatorDefault<ChainBoxData const*, void>, DefaultTypedAllocator<HashMapElement<ChainBoxData const*, Vector<ChainBoxData const*> > > >&, HashMap<OBBCacheKey, Vector<Vector2>, OBBCacheKeyHasher, OBBCacheKeyComparator, DefaultTypedAllocator<HashMapElement<OBBCacheKey, Vector<Vector2> > > >&, List<Vector2, DefaultAllocator>&, Vector<long>&, List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>&, Vector<Vector2> const&) at ./scene/resources/bit_map.cpp:896
[9] 4e4ad23 (main+4a4ad23) - find_intersections(List<List<Vector2, DefaultAllocator>::Element*, DefaultAllocator>&, List<Vector2, DefaultAllocator>&, Vector<long>&, Vector<Vector2>&) at ./scene/resources/bit_map.cpp:1065
[10] 4e4af59 (main+4a4af59) - monotonic_chain_rdp(Vector<Vector2>&, float) at ./scene/resources/bit_map.cpp:1097
[11] 4e4b0d1 (main+4a4b0d1) - reduce(Vector<Vector2>&, Rect2i const&, float, bool) at ./scene/resources/bit_map.cpp:1114
[12] 4e4b5ff (main+4a4b5ff) - BitMap::clip_opaque_to_polygons(Rect2i const&, float, bool) const at ./scene/resources/bit_map.cpp:1211
[13] 4e4baee (main+4a4baee) - BitMap::_opaque_to_polygons_bind(Rect2i const&, float, bool) const at ./scene/resources/bit_map.cpp:1295
[14] 5187752 (main+4d87752) - void call_with_validated_variant_args_retc_helper<__UnexistingClass, TypedArray<Vector<Vector2> >, Rect2i const&, float, bool, (unsigned long)0, (unsigned long)1, (unsigned long)2>(__UnexistingClass*, TypedArray<Vector<Vector2> > (__UnexistingClass::*)(Rect2i const&, float, bool) const, Variant const**, Variant*, IndexSequence<((unsigned long)0, (unsigned long)1, (unsigned long)2)...>) at ./core/variant/binder_common.h:135
[15] 514c6dc (main+4d4c6dc) - void call_with_validated_object_instance_args_retc<__UnexistingClass, TypedArray<Vector<Vector2> >, Rect2i const&, float, bool>(__UnexistingClass*, TypedArray<Vector<Vector2> > (__UnexistingClass::*)(Rect2i const&, float, bool) const, Variant const**, Variant*) at ./core/variant/binder_common.h:423
[16] 51038fa (main+4d038fa) - MethodBindTRC<TypedArray<Vector<Vector2> >, Rect2i const&, float, bool>::validated_call(Object*, Variant const**, Variant*) const at ./core/object/method_bind_common.h:372
[17] 17f6e3b (main+13f6e3b) - GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Callable::CallError&, GDScriptFunction::CallState*) at ./modules/gdscript/gdscript_vm.cpp:2324
[18] 17309a5 (main+13309a5) - GDScriptInstance::callp(StringName const&, Variant const**, int, Callable::CallError&) at ./modules/gdscript/gdscript.cpp:1976
[19] 6574fe2 (main+6174fe2) - Object::callp(StringName const&, Variant const**, int, Callable::CallError&) at ./core/object/object.cpp:799
[20] 621ad13 (main+5e1ad13) - Variant::callp(StringName const&, Variant const**, int, Variant&, Callable::CallError&) at ./core/variant/variant_call.cpp:1406
[21] 17f3c6e (main+13f3c6e) - GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Callable::CallError&, GDScriptFunction::CallState*) at ./modules/gdscript/gdscript_vm.cpp:1979
[22] 17309a5 (main+13309a5) - GDScriptInstance::callp(StringName const&, Variant const**, int, Callable::CallError&) at ./modules/gdscript/gdscript.cpp:1976
[23] 42211a8 (main+3e211a8) - Node::_gdvirtual__input_call(RequiredParam<InputEvent>) at ./scene/main/node.h:441
[24] 41a882e (main+3da882e) - Node::_call_input(Ref<InputEvent> const&) at ./scene/main/node.cpp:3598
[25] 41b8c41 (main+3db8c41) - SceneTree::_call_input_pause(StringName const&, SceneTree::CallInputType, Ref<InputEvent> const&, Viewport*) at ./scene/main/scene_tree.cpp:1478
[26] 41dda2d (main+3ddda2d) - Viewport::push_input(RequiredParam<InputEvent>, bool) at ./scene/main/viewport.cpp:3549
[27] 4204b01 (main+3e04b01) - Window::_window_input(Ref<InputEvent> const&) at ./scene/main/window.cpp:2047
[28] 430aceb (main+3f0aceb) - void call_with_variant_args_helper<Window, Ref<InputEvent> const&, (unsigned long)0>(Window*, void (Window::*)(Ref<InputEvent> const&), Variant const**, Callable::CallError&, IndexSequence<((unsigned long)0)...>) at ./core/variant/binder_common.h:59
[29] 42eb443 (main+3eeb443) - void call_with_variant_args<Window, Ref<InputEvent> const&>(Window*, void (Window::*)(Ref<InputEvent> const&), Variant const**, int, Callable::CallError&) at ./core/variant/binder_common.h:174
[30] 42c765e (main+3ec765e) - CallableCustomMethodPointer<Window, void, Ref<InputEvent> const&>::call(Variant const**, int, Variant&, Callable::CallError&) const at ./core/object/callable_mp.h:107
[31] 6203461 (main+5e03461) - Callable::callp(Variant const**, int, Variant&, Callable::CallError&) const at ./core/variant/callable.cpp:58
[32] 4593e3 (main+593e3) - Variant Callable::call<Ref<InputEvent> >(Ref<InputEvent>) const at ./core/variant/variant.h:929
[33] 444081 (main+44081) - DisplayServerX11::_dispatch_input_event(Ref<InputEvent> const&) at ./platform/linuxbsd/x11/display_server_x11.cpp:4617
[34] 443df3 (main+43df3) - DisplayServerX11::_dispatch_input_events(Ref<InputEvent> const&) at ./platform/linuxbsd/x11/display_server_x11.cpp:4594
[35] 6195365 (main+5d95365) - Input::_parse_input_event_impl(Ref<InputEvent> const&, bool) at ./core/input/input.cpp:1110
[36] 6197882 (main+5d97882) - Input::flush_buffered_events() at ./core/input/input.cpp:1643
[37] 44932a (main+4932a) - DisplayServerX11::process_events() at ./platform/linuxbsd/x11/display_server_x11.cpp:5751
[38] 41ac48 (main+1ac48) - OS_LinuxBSD::run() at ./platform/linuxbsd/os_linuxbsd.cpp:995
[39] 42f6d5 (main+2f6d5) - main at ./platform/linuxbsd/godot_linuxbsd.cpp:122
[40] 7fe430c9b5b5 (libc.so.6+35b5) - /lib64/libc.so.6(+0x35b5) [0x7fe430c9b5b5]
[41] 7fe430c9b668 (libc.so.6+3668) - /lib64/libc.so.6(__libc_start_main+0x88) [0x7fe430c9b668]
[42] 410a75 (main+10a75) - godot.linuxbsd.editor.dev.x86_64() [0x410a75]
-- END OF C++ BACKTRACE --
================================================================
GDScript backtrace (most recent call first):
    [0] _do_plot (res://main.tscn::GDScript_3tay1:26)
    [1] _input (res://main.tscn::GDScript_3tay1:62)
-- END OF GDSCRIPT BACKTRACE --
================================================================

So far it does seem to solve at least some of the cases that broke with the pre-existing algorithm, but does seem to have some edge cases where it fails, but I will continue some testing tomorrow probably and look at some of the code

Edit:
Here's an updated testing rig to use, press Q to toggle advanced_rdp on and off (starts out off) and S to dump the data of the bitmap to help reproduce edge cases etc. like the one above, left mouse to place a square, right to clear one, escape to clear the board, and space to generate a polygon from the points

BitPR3.zip

Prevent lines from being added to OBB cache.
Added guards against colinear points edge case.
- Also tries to avoid adding a colinear point on top of the guards.
Add quick fix against vertical starting segments.
Adjust high_speed_perp_dist to copy. perpendicular_distance 's style.
Replace manual binary search with built-in.
Avoid recursing down chain if no chain is added.
Clear edited chain for recalcing obb.
@Sinowa-Programming

Copy link
Copy Markdown
Author

I fixed the edge case that you had mentioned( it was from me directly added all generated OBBs, even the lines, to the cache ). I have also adjusted most of the functions to have defensive programming checks. Co-linear inputs have been protected against and as well as vertical start segments.

@Mickeon
Mickeon removed request for a team July 24, 2026 06:43
@AThousandShips

Copy link
Copy Markdown
Member

I'll take a look either today or next week!

@AThousandShips

Copy link
Copy Markdown
Member

Did some light testing and will test some more next week but did run into one cases that fails with both algorithms: { "size": Vector2i(36, 20), "data": [0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 56, 232, 0, 128, 193, 246, 3, 128, 15, 70, 17, 0, 44, 56, 22, 7, 96, 130, 48, 71, 224, 35, 204, 193, 0, 2, 102, 0, 24, 32, 228, 195, 193, 0, 118, 4, 54, 6, 192, 197, 48, 54, 0, 192, 136, 193, 1, 0, 132, 15, 0, 0, 192, 16, 6, 0, 0, 12, 35, 0, 0, 128, 225, 2, 0, 0, 112, 56, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0] }

It looks like this

image

But I will look more next week was just messing around with it, will see if I can look at some more robust ways to identify different edge cases for it

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BitMap.opaque_to_polygons can still create invalid polygons

10 participants