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 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.
The basic low-level API provided by Pogamut assumes these 3 stages:
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))
.
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.
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.
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 →
→
→
→ .
The Raycasting usage has these stages:
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()); }
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.
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.
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();
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.
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
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.