Using rotated areas in filtered searches

Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Using rotated areas in filtered searches

Post by Pi-C »

TLDR
I want to be able to use a rotated area for surface searches.
Why?
Currently, we can pass on area, position, radius, and direction. All of these have limitations that either make them unsuitable for my purposes:
  • "area" will find any entities with any part of their collision_box within the area. However, the area must be a rectangle with a base that's running parallel to the x-axis.
  • "position" will find the entity which has that position within its collision_box.
  • "position" + "radius" will find any entity within the given radius around the position, iff the entity's center is within the radius.
  • "direction" will find any entity in the given direction from a given position, but apparently it's not possible to limit the search distance.
The workaround is to search repeatedly with different arguments (e.g. one search in an area, followed by several searches by position) until a search produces a result. This involves a lot of overhead for calculating the area and the individual positions for each search, which comes on top of the costs for running each search.

Being able to pass on a position, a bounding box and the orientation of that box would reduce the overhead to calculating bounding box, orientation, and position just once (sometimes they can be directly read from an entity) and just one search. Given that operations handled directly by the game are generally cheaper in terms of computing time than anything that can be done with Lua, this could give a speed boost to mods.
What should this be applied to?
I'm mostly interested in surface.find_entities() and surface.find_entities_filtered(), but there are some other things that are similar enough (basically: any search involving "area") where searching rotated areas would make sense:
  • find_entities(), find_entities_filtered, count_entities_filtered
  • find_tiles_filtered, count_tiles_filtered
  • find_units
  • find_decoratives_filtered
Use case
Autodrive allows vehicles based on "car" and "spider-vehicle" prototypes to drive autonomously. Sometimes a vehicle will crash into an obstacle that it can't or shouldn't destroy. In this case, it would be stuck because it's still heading into a direction where it can't move. The solution is to let the vehicle bounce off the obstacle until it comes to a stop, and try to repath from that position.

The way I currently do the bouncing is rather naive: If a vehicle can't go on, I give it a fixed negative bounce speed, and make this speed positive if the vehicle already was in reverse. However, it could happen that a bouncing vehicle would crash into something else and bounce off that again, with the same fixed speed, eventually being stuck in a state where it continually moves between the same two positions.

Now I want to improve this, especially in regard to crashes of a vehicle with another one. In order to calculate the bounce speeds in a (somewhat) reasonable way, I must know whether they've crashed while driving towards each other (front/front or front/back while reversing) or in the same direction (front/back or front/front while reversing). Therefore, I must check whether the damaged vehicle is in front of or behind the one that caused the crash (and vice versa).

The following screenshots visualize the way searching for entities is done. The thin red lines are the collision boxes (activated via debug settings). The white lines are the x- and y-axes of the vehicles, with the vehicle center as position {0, 0}. Yellow drawings show the search area in front, red drawings (thick lines) the search area at the back of a vehicle. The text at the bottom left indicates where each vehicle found the other one ("front", "back", or "nil").


I started out by looking in a circle (diameter: the smaller side of the vehicle bounding box) just a bit outside of the vehicle's collision_box. This wasn't reliable if the other entity's center wasn't within the circle. Increasing the radius also isn't feasible because then entities might be found while looking ahead although they were actually behind:
Tank doesn't see car, car doesn't see tank.
Tank doesn't see car, car doesn't see tank.
crash_radius.png (1.72 MiB) Viewed 1144 times

Then I decided to use area searches if a vehicle was oriented in one of the four cardinal directions (vehicle.orientation: 0, 0.25, 0.5, 0.75). This works, but I can only use this in exceptional cases as the vehicles will be oriented differently most of the time:
Tank doesn't see car searching within radius when car center is outside the circle. Car searching by area sees tank.
Tank doesn't see car searching within radius when car center is outside the circle. Car searching by area sees tank.
radius+area.png (1.98 MiB) Viewed 1144 times

A further improvement was looking at positions in a straight line from the vehicle center in the direction given by vehicle.orientation. If the first search doesn't find anything it may be because the angle is too awkward and the point isn't within the other vehicle's collision_box yet, so I extend the distance up to 5 times before giving up. This will work most of the time:
Searching by position, both tank and car can see each other.
Searching by position, both tank and car can see each other.
crash_points.png (2.44 MiB) Viewed 1144 times

However, there are cases where the two vehicles are at such an angle to each other that searching in one line from the center of a vehicle will never find the other one:
Tank doesn't see car looking ahead in straight line. Car searching an area sees the tank.
Tank doesn't see car looking ahead in straight line. Car searching an area sees the tank.
crash_points_center.png (1.43 MiB) Viewed 1144 times

I could work around this by searching a grid: Suppose the collision_box is a rectangle with points A, B, C, D (counting counterclockwise from bottom_left). Go from vehicle center to the center of the line B-C. If nothing turns up there, look at B, if nothing is there look at C. If no entity has been found, extend the search until an area of 3x5 points has been searched. For modded vehicles with a huge collision_box, this search grid may still not be fine-meshed enough, so it may be necessary to add even more search positions in each line.

Basically, having to search in this way is an approximation of searching an arbitrarily rotated box. It could be done, but in a real game (not under lab conditions where the world is empty except for some random entities cheated for testing) this probably would be a UPS-hog. If the game gave us the means to search within rotated areas natively, this would definitely improve performance.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
User avatar
boskid
Factorio Staff
Factorio Staff
Posts: 3290
Joined: Thu Dec 14, 2017 6:56 pm
Contact:

Re: Using rotated areas in filtered searches

Post by boskid »

Pi-C wrote: Sat May 06, 2023 1:20 pm [*] "direction" will find any entity in the given direction from a given position, but apparently it's not possible to limit the search distance.
I have no idea who told you about such mode because there is no such search type.

In the code there are exactly 4 primary search code paths: 1/ Position and radius (when 'position' was given and 'radius' is greater than 0), 2/ Position (when `position' was given but radius is either empty or 0), 3/ BoundingBox (when 'position' was not provided but 'area' was provided) and 4/ Entire surface (when neither 'position' nor 'area' was given).

"direction" parameter is a filter, if entity's LuaEntity::direction is not equal to that direction (or is not in the array of directions since it also accepts a table of directions) the entity will not be returned.

The only feature missing here to make rotated areas usable with entity searches is that parsing BoundingBox from lua is not yet accepting the orientation.
User avatar
boskid
Factorio Staff
Factorio Staff
Posts: 3290
Joined: Thu Dec 14, 2017 6:56 pm
Contact:

Re: Using rotated areas in filtered searches

Post by boskid »

Ok, starting from 1.1.82 the BoundingBox parsing from lua will also check the orientation.
example
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Using rotated areas in filtered searches

Post by Pi-C »

boskid wrote: Sat May 06, 2023 3:27 pm I have no idea who told you about such mode because there is no such search type. […] "direction" parameter is a filter, if entity's LuaEntity::direction is not equal to that direction (or is not in the array of directions since it also accepts a table of directions) the entity will not be returned.
Searching for similar requests, I found that old post of mine that I've linked to. When I wrote that I thought "direction" was about looking in a certain direction. I didn't realize this was a filter for sieving out entities that have another direction. Looking at the API description didn't help a lot either:

Code: Select all

direction	:: defines.direction or array[defines.direction]?
Perhaps it would be a good idea to explain that more clearly -- but we've got another thread for this.

boskid wrote: Sat May 06, 2023 3:44 pm Ok, starting from 1.1.82 the BoundingBox parsing from lua will also check the orientation.
Thank you very much, this really is great news! This feature will help me so much -- not only for the example described above, but also when checking for rails and trains in the direction a vehicle is driving. :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Post Reply

Return to “Implemented mod requests”