Say I have an agent in 2D in a world filled with circular obstacles of different kind and across many scales (some may be very large, practically filling an agent's view, some may be very small).
The agent has some vision cone and can these obstacles and their kinds if they are within that cone.
I would like to be able to test how much of the agent's cone is occupied by each sort of object and in what direction this happens.
I have already worked out what it takes to find how large a circle would appear in the field of vision of an agent. However, currently the agent can also just see through any object and also react to whatever is behind (the ultimate result is an average weighted by how large each kind of object appears)
Physics2D.OverlapCircleNonAlloc and an angle-based second check to see if things are still in the vision cone (and how big they are in that cone), I can find all seen objects in order of closeness and give them the appropriate weighting.
Currently I'm not doing anything with the ordering information.
I think what I roughly need to do is to define an interval, and fill it up with each object kind (something like a tuple of the kind index, and the left and right bounds of the interval) and check that no previous object already fell in those interval bounds. (If that part of the interval is already covered, clip to parts of the space that are still free) After that I'd have to loop over each segment of the interval and accumulate the weighted average view accordingly.
However, that sounds quite slow. I have potentially hundreds of such agents able to see any kind of obstacle including each other, and even the simpler current vision system is definitely a bottleneck.
Are there ways to do this faster? (Keep in mind that each obstacle is a potentially small 2D circle that appears as a 1D line to an agent)
To get the part of an object that's in the field of vision, I'm currently doing roughly this:
float r = object.GetObjectRadius(); // the radius of the seen object
Vector2 d = this.transform.position - base.transform.position; // "this" being the seen object, "base" the agent. The vector from the agent to the target)
float rd2 = Mathf.Min(r * r / d.sqrMagnitude, 1f); // if it's smaller than 1, you are inside the object. Pretend you're just touching it
Vector2 d_tangent = d * (1f - rd2);
Vector2 d_perp_tangent = Vector2.Perpendicular(d) * rd2 * Mathf.Sqrt(1f / rd2 - 1f);
Vector2 left_tangent = d_tangent + d_perp_tangent;
Vector2 right_tangent = d_tangent - d_perp_tangent
float fov_overlap = Mathf.Min(Vector2.SignedAngle(agentUpVect, left_tangent), half_view) + Mathf.Min(Vector2.SignedAngle(right_tangent, agentUpVect), half_view) // agentUpVect is the direction the agent faces in, half_view is half the vision cone. If fov_overlap < 0, the object is not seen.
This should give me the precise part of the view that's occupied by a circular object of radius
r which is
|d| units away from an agent.
I tried figuring out the above myself and I'm fairly confident it's right. I verified this by also implementing this math in GeoGebra:
EDIT: Adapted from @Mangata, here is a simplified schematic of what is needed:
for simplicity, I have normalized it here such that the view is in the interval [-1, 1] and anything outside that gets discarded. You can see here three different kinds of objects and "the horizon" all of which need to correctly handle occlusions. In the end I'd like to have the total length of the intervals of each kind of "color" (note, these are a small number of fixed labels for me, one per kind of object, not actually RGB colors. For instance, black might be another agent, small but close by, red might be food, and green poison. The blue part is nothing / "the horizon")