Tutorial body

Previous tutorial has shown us how to use navigation graph for planning movement of the bot. This tutorial presents alternative approach that can be used when you don't have navigation graph at all or when the graph doesn't provide all the necessary information.

Raycasting

Raycasting is technique commonly known from computer graphics based on analytical geometry. The goal of raycasting is to compute intersection of ray and geometry of the world. Simple high school analytical geometry can be used for implementation of raycasting but this isn't topic of this tutorial. If you are interested in math behind raycasting read for example this TODO Wikipedia article. This tutorial is concerned with using raycasting in Pogamut and Unreal Tournament.

Configuring rays - low level API

The basic low-level API provided by Pogamut assumes these 3 stages:

  1. Enable raycasting feature and rays visualization

    If you want to start continuous computation of ray X world intersections set AutoTrace parameter of the bot to true, you can do this by getAct().act(new Configuration().setAutoTrace(true)); method call.

    For debugging it is handy to enable also DrawTraceLines feature, this way rays you will set up will be visualized in UT. So you can extend the previous call to getAct().act(new Configuration().setDrawTraceLines(true).setAutoTrace(true)).

  2. Initialize rays

    Set the ray by AddRay command, eg. by getAct().act(new AddRay("LEFT45", new Vector3d(1, -1, 0), 500, true, true, true));. Exact meaning of the parameters is described in JavaDoc.

  3. Handle results

    Once the AddRay was send the UT computes the result and returns it in AutoTraceRay message. You can obtain this object by:

    • registering listener that will store handle to the ray result in local variable through custom method setLeftRay(...)

                  getWorldView().addObjectListener(AutoTraceRay.class, 
                          WorldObjectFirstEncounteredEvent.class, 
                          new IWorldObjectListener<AutoTraceRay, WorldObjectFirstEncounteredEvent<AutoTraceRay>>() {
      
                      public void notify(WorldObjectFirstEncounteredEvent event) {
                          if(event.getId().equals("LEFT45")) {
                              setLeftRay((AutoTraceRay)event.getObject());
                          }
                      } 
                  });
    • OR by checking presence of the object in world view periodically, eg. in doLogic() method

                  AutoTraceRay ray = null;
                  StringId rayId = new StringId("LEFT45");
      
                  ...
      
                  if(ray == null) {
                      // ray has not been initialized yet, try to obtain the instance
                      ray = getWorldView().getAll(AutoTraceRay.class).get(rayId);
                  }

    Once you obtain the AutoTraceRay object it will be automatically updated by the platform when new results arrive.

Configuring rays - Raycasting facade

As you can see the process of initializing rays is quite complex, to ease this process there is a Raycasting class that is utilized also by 03-RaycastingBot example. First let's open the example, you will find it under FileNew Project ...SamplesPogamut03-RaycastingBot.

The Raycasting usage has these stages:

  1. Create instance of Raycasting, the example bot does this in prePrepareBot() method:

        @Override
        protected void prePrepareBot() {
            getLogger().setLevel(Level.ALL);
            // Raycasting object is utility object for initializing multiple rays at once
            raycasting = new Raycasting(this);
            // advanced locomotion will be used to move the bot, it is a faccade
            // for sending various commands
            loc = new AdvancedLocomotion(this, getLogger().platform());
        }
  2. Initialize multiple rays at once, RaycastingBot does this in postPrepareBot() method but you can do it in doLogic() or prePrepareBot() as well:

                // 1. remove all previous rays, each bot starts by default with three
                // rays, for educational purposes we will set them manually
                getAct().act(new RemoveRay("All"));
    
                // 2. create new rays
                raycasting.createRay(LEFT90, new Vector3d(0, -1, 0), rayLength, fastTrace, floorCorrection, traceActor);
                raycasting.createRay(LEFT45, new Vector3d(1, -1, 0), rayLength, fastTrace, floorCorrection, traceActor);
                raycasting.createRay(FRONT, new Vector3d(1, 0, 0), rayLength, fastTrace, floorCorrection, traceActor);
                raycasting.createRay(RIGHT45, new Vector3d(1, 1, 0), rayLength, fastTrace, floorCorrection, traceActor);
                raycasting.createRay(RIGHT90, new Vector3d(0, 1, 0), rayLength, fastTrace, floorCorrection, traceActor);
    

    You can notice that first all previously added rays were removed (this is done just to be sure that we are starting on clear ground), then 5 new rays were created.

  3. Register listener that will be called after all rays were initialized (first result of computation come from UT):

                // register listener called when all rays are set up in the UT engine
                raycasting.getAllRaysInitialized().addListener(new FlagListener<Boolean>() {
    
                    public void flagChanged(Boolean changedValue) {
                        // once all rays were initialized store the AutoTraceRay objects
                        // that will come in response in local variables, it is just
                        // for convenience
                        left = raycasting.getRay(LEFT45);
                        front = raycasting.getRay(FRONT);
                        right = raycasting.getRay(RIGHT45);
                    }
                });

    This is the advantage of using Raycasting object over previously presented low level API. You don't have to manually code mechanism that will wait for initialization of all rays.

  4. Inform Raycasting instance that you don't intend to register any new rays (so the counting of incoming AutoTraceRay objects may start):

                // 3. declare that we are not going to setup any other rays
                raycasting.endRayInitSequence();
    

Navigating using raycasting

Navigation bot uses simple reactive algorithm of navigation that can be in brief described as IF no ray is signalling THEN go forward ELSE move in opposite direction of signalling ray. The algorithm is coded in doLogic() method.

Observing raycasting bot in UT

Now let's see how the bot is doing. Start server with DM-TrainingDay map by executing startGamebotsDMServer.bat. Then switch to spectate mode by right clicking server node in Netbeans and selecting Spectate action. After the UT client loads you will see the raycasting bot navigating through the environment. The bot will have five rays although only three of them are used by the navigation algorithm. When the ray is green then it hasn't collided with any wall or actor, otherwise it will turn red.