Implementing a deferred raycaster

The Querying the world recipe is a good proof of the potential of raycasting within a physics game environment. However, sometimes you might perform too many simultaneous queries within the same game loop iteration, so the CPU is not able to process them fast enough and makes a small freeze visible on the screen.

A solution would be to implement a queue of raycast queries, ordered by priority, with the particularity of having a limited time per frame to dispatch as much as it can, keeping the rest for future iterations. The higher the priority, the sooner it will be executed.

Getting ready

In order to keep things tidy, the code will be split into two files:

  • RayCastManager.java to implement the deferred raycaster
  • Box2DDefereedRaycasterSample.java shows a practical usage case of the aforementioned class

If you do not feel comfortable with world queries, I strongly suggest you to go back and review the Querying the world recipe.

How to do it…

This recipe will not have too much visual interaction but a lot of log data so all the useful information will be printed in the console.

The RayCastManager class

As the definition of the RayCastManager class is written in the introduction to this recipe, we will just go to the class creation process:

  1. First of all, we will encapsulate all the data that refer to a raycasting request into an object whose class definition will be as follows:
    private class RayCastRequest {
      final public int priority;
      final public Vector2 point1;
      final public Vector2 point2;
      final public RayCastCallback callback;
    
      public RayCastRequest(int priority, Vector2 point1, Vector2 point2, RayCastCallback callback) {
        this.priority = priority;
        this.point1 = point1;
        this.point2 = point2;
        this.callback = callback;
      }
    }

    Field names should be self-explanatory for you. They are declared as final to make those references immutable.

  2. Now we can easily declare the necessary properties for our main class to carry out its mission:
    private final static float SECONDS_TO_NANO =  1000000000f;
    private float budgetTime;
    private World world;
    private PriorityQueue<RayCastRequest> requestQueue;
    • budgetTime: This is the limit time (in seconds) for processing requests in each update tick
    • SECONDS_TO_NANO: This is a constant to perform conversions
    • requestQueue: This will store all the pending queries

    Note

    A PriorityQueue is a Java built-in data type defined as an unbounded priority queue based on a priority heap where the head will be the element with the least priority.

  3. Once the demands for this class are clear, its constructor will ask for World and budgetTime. The requestQueue will be internally created with the help of a Comparator class instance to order requests by priority:
    public RayCastManager(World world, float budgetTime) {
      this.world = world;
      this.budgetTime = budgetTime;
      this.requestQueue = new PriorityQueue<RayCastRequest>(1, new Comparator<RayCastRequest>() {
        public int compare(RayCastRequest r1, RayCastRequest r2) {
          return r2.priority – r1.priority; 
        }
      });
    }

    Please realize that the subtract order of elements in the compare method is reversed to make the highest priority element the head of the queue.

  4. We must also provide the final user with a function to add requests as they need it:
    public boolean addRequest(int priority, Vector2 point1, Vector2 point2, RayCastCallback callback) {
      return requestQueue.add(new RayCastRequest(priority, new Vector2(point1), new Vector2(point2), callback));
    }
  5. Finally, RayCastManager must have a mechanism to start processing as many queries as budgetTime allows:
    public void update() {
      long now = TimeUtils.nanoTime();
      RayCastRequest rr = requestQueue.poll();
      while(rr != null && 
        TimeUtils.timeSinceNanos(now) < budgetTime * SECONDS_TO_NANO) { 
          world.rayCast(rr.callback, rr.point1, rr.point2);
          rr = requestQueue.poll();
        }
      }

    This function basically gets the current value of the system time in nanoseconds and extracts the head value from the queue. Next, a loop starts ensuring that there are still some requests left to process and that budgetTime is not spent. Then, the query is made and a new iteration starts with the next head value.

Using RayCastManager

As you would have seen, the usage of RayCastManager is very simple:

  1. To start with, declare the following member field:
    RayCastManager raycastManager;
  2. Initialize it by calling its constructor. We will give RayCastManager a maximum of 0.1 seconds per tick to process requests:
    raycastManager = new RayCastManager(world, 0.1f);
  3. Add some requests to the queue. Do not forget to previously define a callback as explained in the Querying the world recipe:
    raycastManager.addRequest(priority, point1, point2, callback);
    …
  4. Update it in the render function:
    raycastManager.update();

How it works…

Too many words and code might move you away from the simplicity of this recipe. A good diagram will help:

How it works…

There's more…

Although your RayCastManager is quite impressive, there are still other ways of improving it. One of them could be to add support for AABB queries. Do you dare?

See also

  • The The fixed timestep approach recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.204.186