View Javadoc

1   package Steerings;
2   
3   
4   import SteeringStuff.SteeringManager;
5   import SteeringStuff.RefBoolean;
6   import SteeringProperties.ObstacleAvoidanceProperties;
7   import SteeringProperties.SteeringProperties;
8   import SteeringStuff.IRaysFlagChanged;
9   import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
10  import java.util.concurrent.ExecutionException;
11  import java.util.logging.Level;
12  import java.util.logging.Logger;
13  import javax.vecmath.Vector3d;
14  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
15  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.AutoTraceRay;
16  import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
17  import SteeringStuff.ISteering;
18  import SteeringStuff.RaycastingManager;
19  import SteeringStuff.SteeringRay;
20  import SteeringStuff.SteeringTools;
21  import SteeringStuff.SteeringType;
22  import java.util.HashMap;
23  import java.util.LinkedList;
24  import java.util.Random;
25  import java.util.concurrent.Future;
26  import javax.vecmath.Tuple3d;
27  import javax.vecmath.Vector2d;
28  
29  
30  /**
31   * A class for providing obstacle avoiding steering via raycasting.
32   *
33   * @author Marki
34   */
35  public class ObstacleAvoidanceSteer implements ISteering, IRaysFlagChanged {
36  
37      /**This steering needs UT2004Bot (to get velocity and location). */
38      private UT2004Bot botself;
39      /**This steering needs raycasting manager (it uses 5 rays). */
40      private RaycastingManager rayManager;
41      
42      /**Steering properties: the magnitude of the repulsive force from the obstacles.
43       * Reasonable values are 0 - 1000, the default value is 240.*/
44      private int repulsiveForce;
45      /**Steering properties: the order of the force. Possible values are 1 - 10, the default value is 1.
46       * The curve of reactions to obstacles according to the order 1 is linear, 2 quadratic, etc.
47       * It means that with higher order, the bot reacts less to dsitant obstacles and more to near obstacles.
48       * But the value 1 is most usefull value. Other values can cause strange behaviour alongside walls etc.*/
49      private int forceOrder;
50      /**Steering properties: the switch front collisions. The default value (in basic baheviour) is false.
51       * Special solution of head-on collisions (front collisions). Basic behaviour leads to rebounding from the obstacles
52       * (when the bot aims to the obstacle head-on, he turns nearly 180° round just in front of the obstacle).
53       * When this parameter is on, bot turns and continues alongside the side of the obstacle.*/
54      private boolean frontCollisions;
55      /**Steering properties: the switch tree collisions. The default value (in basic baheviour) is false.
56       * Special solution of collisions with trees and other narrow obstacles (so narrow, that just one of the rays will hit them).
57       * In basic behaviour (when the switch is off), when the bot aims to the tree that just the front side rays hits, he avoids the tree from the worse side.
58       * When the switch is on, he avoids the obstacle from the right (nearer) side.*/
59      private boolean treeCollisions;
60  
61  
62      private static double FRONT_RAY_WEIGHT = 1;
63      private static double SIDE_FRONT_RAY_WEIGHT = 0.8;
64      private static double SIDE_RAY_WEIGHT = 0.5;
65  
66  
67      /** How long is the vector of walking velocity. Used for rescaling normal vector, when hitting an obstacle with a ray. */
68      //protected static final double repulsiveForce=240;
69  
70      /**When rays are ready (AutoTraceRays are set), the raysReady is true.*/
71      private boolean raysReady;
72      /**Map with Future AutoTraceRays.*/
73      private HashMap<String, Future<AutoTraceRay>> myFutureRays;
74      
75      // Constants for rays' ids
76      protected static final String LEFTFRONT = "oleftfront";
77      protected static final String LEFT = "oleft";
78      protected static final String RIGHTFRONT = "orightfront";
79      protected static final String RIGHT = "oright";
80      protected static final String FRONT = "ofront";
81      //protected static final String LEFTFRONT2 = "oleftfront2";   //delete
82      //protected static final String RIGHTFRONT2 = "orightfront2";   //delete
83  
84      // Whether sensors signalize the collision.
85      boolean sensorLeft = false;
86      boolean sensorRight = false;
87      boolean sensorLeftFront = false;
88      boolean sensorRightFront = false;
89      boolean sensorFront = false;
90      //boolean sensorLeftFront2 = false;   //delete
91      //boolean sensorRightFront2 = false;   //delete
92  
93      /** Bot's rays. */
94      AutoTraceRay left, right, leftfront, rightfront, front;
95      //AutoTraceRay leftfront2, rightfront2;   //delete
96      
97      // Lengths of bot's rays. Short rays are supposed for side rays, long rays for front rays.
98      final int shortRayLength = (int) (UnrealUtils.CHARACTER_COLLISION_RADIUS * 8); //5
99      final int longRayLength = (int) (UnrealUtils.CHARACTER_COLLISION_RADIUS * 25);  //15
100     //final int longRayLength2 = (int) (UnrealUtils.CHARACTER_COLLISION_RADIUS * 15);  //   //delete
101 
102     /**Just random. Used in frontCollisions - whether turn left or right if it isn't clear.*/
103     Random random = new Random();
104     
105     /**
106      * 
107      * @param bot - instance (UT2004Bot) of the steered bot.
108      * @param rayManager - RaycastingManager of the steered bot.
109      */
110     public ObstacleAvoidanceSteer(UT2004Bot bot, RaycastingManager rayManager) {
111         botself = bot;
112         this.rayManager = rayManager;
113         prepareRays();
114     }
115 
116     //Prepares rays for the bot, removes any old rays and sets new ones.
117     private void prepareRays() {
118         //Five rays are created.
119         LinkedList<SteeringRay> rayList = new LinkedList<SteeringRay>();
120         rayList.add(new SteeringRay(LEFT, new Vector3d(0, -1, 0), shortRayLength));
121         rayList.add(new SteeringRay(LEFTFRONT, new Vector3d(Math.sqrt(3) * 2, -1, 0), longRayLength));
122         rayList.add(new SteeringRay(RIGHTFRONT, new Vector3d(Math.sqrt(3) * 2, 1, 0), longRayLength));
123         rayList.add(new SteeringRay(RIGHT, new Vector3d(0, 1, 0), shortRayLength));
124         rayList.add(new SteeringRay(FRONT, new Vector3d(1, 0, 0), longRayLength));
125         //rayList.add(new SteeringRay(LEFTFRONT2, new Vector3d(Math.sqrt(3), -1, 0), longRayLength2));   //delete
126         //rayList.add(new SteeringRay(RIGHTFRONT2, new Vector3d(Math.sqrt(3), 1, 0), longRayLength2));   //delete
127         rayManager.addRays(SteeringType.OBSTACLE_AVOIDANCE, rayList, this);
128         raysReady = false;
129     }
130 
131     public void flagRaysChanged() {
132         myFutureRays = rayManager.getMyFutureRays(SteeringType.OBSTACLE_AVOIDANCE);
133         raysReady = false;
134         listenToRays();
135     }
136 
137     /**When the rays are ready, we can set the AutoTraceRays.*/
138     private void listenToRays() {
139         for(String rayId : myFutureRays.keySet()) {
140             Future<AutoTraceRay> fr = myFutureRays.get(rayId);
141             if (fr.isDone()) {
142                 try {
143                     if (rayId.equals(LEFTFRONT)) {
144                         leftfront = fr.get();
145                     } else if (rayId.equals(RIGHTFRONT)) {
146                         rightfront = fr.get();
147                     } else if (rayId.equals(LEFT)) {
148                         left = fr.get();
149                     } else if (rayId.equals(RIGHT)) {
150                         right = fr.get();
151                     } else if (rayId.equals(FRONT)) {
152                         front = fr.get();
153                     }/* else if (rayId.equals(LEFTFRONT2)) {   //delete
154                         leftfront2 = fr.get();
155                     } else if (rayId.equals(RIGHTFRONT2)) {   //delete
156                         rightfront2 = fr.get();
157                     }*/
158                     raysReady = true;
159                 } catch (InterruptedException ex) {
160                     Logger.getLogger(ObstacleAvoidanceSteer.class.getName()).log(Level.SEVERE, null, ex);
161                 } catch (ExecutionException ex) {
162                     Logger.getLogger(ObstacleAvoidanceSteer.class.getName()).log(Level.SEVERE, null, ex);
163                 }
164             }
165         }        
166     }
167 
168     
169     /** When called, the bot starts steering, when possible, he walks straight, he steers away from obstacles though, when necessary. */
170     public Vector3d run(Vector3d scaledActualVelocity, RefBoolean wantsToGoFaster, RefBoolean wantsToStop, Location focus) {
171 
172         wantsToGoFaster.setValue(false);
173         Vector3d nextVelocity = new Vector3d(0,0,0);
174         
175         if (!raysReady) {
176             listenToRays();
177             return nextVelocity;
178         }        
179 
180         //The bot's velocity is received.
181         Vector3d actualVelocity = botself.getVelocity().getVector3d();
182         Vector3d originalVelocity = botself.getVelocity().getVector3d();
183         originalVelocity.normalize();
184         originalVelocity.scale(0);
185         
186         //The bot checks his sensors
187         if (leftfront != null)
188             sensorLeftFront = leftfront.isResult();
189         if (rightfront != null)
190             sensorRightFront = rightfront.isResult();
191         if (left != null)
192             sensorLeft = left.isResult();
193         if (right != null)
194             sensorRight = right.isResult();
195         if (front != null)
196             sensorFront = front.isResult();
197         /*if (leftfront2 != null)
198             sensorLeftFront2 = leftfront2.isResult();   //delete
199         if (rightfront2 != null)
200             sensorRightFront2 = rightfront2.isResult();   //delete
201         */
202         /*
203          * Whether any sensor is signalling or not. Used mostly when determining, whether to return to normal speed. After hitting a wall,
204          * the bot's velocity can be very low, but he can speed up, obviously. When no rays are conflicting, the bot returns to his normal walking speed.
205          */
206         boolean sensor = sensorFront || sensorLeft || sensorLeftFront || sensorRight || sensorRightFront;
207         //sensor = sensor || sensorLeftFront || sensorRightFront;   //delete
208         
209         /*
210          * Normal vector is multiplied by this when applied to the bot. The closer the bot is to the wall (not perpendicularily, the important
211          * thing is the distance from him to the point of conflict with a ray), the bigger this multiplier is, i.e. the stronger the repulsive force is.
212          * It is counted as rl-bdw*2/rl, where rl is a length of ray (short or long one), bdw is bot's distance from wall. It is chosen this way
213          * because when the ray is half in the wall, the normal is applied with it's "natural" force - when bot's further, the force is weaker.
214          * Multiplier - because the closer we are to the wall, the stronger the repulsive power is.
215          */
216         double multiplier;
217         
218         // Normal to the point, where bot's ray intersect with wall.
219         Vector3d normal;
220 
221         // A vector from the point where one of bot's rays crosses the wall to the bot.
222         Vector3d botToHitLocation;
223         
224         /*
225          *All sensors are checked respectively. When any of them signalizes a hit, a vector is added to nextvelocity.
226          *The vector added is derived from normal vector of the wall, such that when the ray is half in the wall (see multiplier),
227          *the steering power is as powerful as walking power at maximal strength.
228          */
229         if (sensorLeft) {
230             botToHitLocation = new Vector3d(left.getHitLocation().x - botself.getLocation().x, left.getHitLocation().y - botself.getLocation().y, 0);
231             normal = left.getHitNormal();
232             multiplier = Math.pow((shortRayLength - botToHitLocation.length()) * 2f / shortRayLength, forceOrder); //How big part of the shortRay is inside the obstacle = (shortRayLength - botToHitLocation.length()) / shortRayLength.
233             normal.scale(SIDE_RAY_WEIGHT * repulsiveForce * multiplier);
234             nextVelocity.add((Tuple3d) normal);
235         }
236         if (sensorRight) {
237             botToHitLocation = new Vector3d(right.getHitLocation().x - botself.getLocation().x, right.getHitLocation().y - botself.getLocation().y, 0);
238             normal = right.getHitNormal();
239             multiplier = Math.pow((shortRayLength - botToHitLocation.length()) * 2f / shortRayLength, forceOrder);
240             normal.scale(SIDE_RAY_WEIGHT * repulsiveForce * multiplier);
241             nextVelocity.add((Tuple3d) normal);
242         }
243         if (sensorLeftFront) {
244             botToHitLocation = new Vector3d(leftfront.getHitLocation().x - botself.getLocation().x, leftfront.getHitLocation().y - botself.getLocation().y, 0);
245             multiplier = Math.pow((longRayLength - botToHitLocation.length()) * 2f / longRayLength, forceOrder);
246             normal = leftfront.getHitNormal();
247             if (treeCollisions && (!sensorRightFront && SteeringTools.pointIsLeftFromTheVector(actualVelocity, normal) ) ) {
248                 normal = computeTreeCollisionVector(normal);
249             }
250             if (frontCollisions && !sensorFront) {
251                 normal = computeFrontCollisionVector(normal);
252             }
253             normal.scale(SIDE_FRONT_RAY_WEIGHT * repulsiveForce * multiplier);
254             nextVelocity.add((Tuple3d) normal);
255         }
256         if (sensorRightFront) {
257             botToHitLocation = new Vector3d(rightfront.getHitLocation().x - botself.getLocation().x, rightfront.getHitLocation().y - botself.getLocation().y, 0);
258             multiplier = Math.pow((longRayLength - botToHitLocation.length()) * 2f / longRayLength, forceOrder);
259             normal = rightfront.getHitNormal();
260             if (treeCollisions && (!sensorLeftFront && !SteeringTools.pointIsLeftFromTheVector(actualVelocity, normal) ) ) {
261                 normal = computeTreeCollisionVector(normal);
262             }
263             if (frontCollisions && !sensorFront) {
264                 normal = computeFrontCollisionVector(normal);
265             }
266             normal.scale(SIDE_FRONT_RAY_WEIGHT * repulsiveForce * multiplier);
267             nextVelocity.add((Tuple3d) normal);
268         }
269         if (sensorFront) {
270             botToHitLocation = new Vector3d(front.getHitLocation().x - botself.getLocation().x, front.getHitLocation().y - botself.getLocation().y, 0);
271             multiplier = Math.pow((longRayLength - botToHitLocation.length()) * 2f / longRayLength, forceOrder);
272             normal = front.getHitNormal();
273             if (frontCollisions) {
274                 normal = computeFrontCollisionVector(normal);                
275             }
276             normal.scale(FRONT_RAY_WEIGHT * repulsiveForce * multiplier);
277             nextVelocity.add((Tuple3d) normal);
278         }
279         /*if (sensorLeftFront2) {
280             botToHitLocation = new Vector3d(leftfront2.getHitLocation().x - botself.getLocation().x, leftfront2.getHitLocation().y - botself.getLocation().y, 0);
281             multiplier = Math.pow((longRayLength2 - botToHitLocation.length()) * 2f / longRayLength2, forceOrder);
282             normal = leftfront2.getHitNormal();
283             if (treeCollisions && (!sensorRightFront2 && SteeringTools.pointIsLeftFromTheVector(actualVelocity, normal) ) ) {
284                 normal = computeTreeCollisionVector(normal);
285             }
286             if (frontCollisions && !sensorFront) {
287                 normal = computeFrontCollisionVector(normal);
288             }
289             normal.scale(SIDE_FRONT_RAY_WEIGHT * repulsiveForce * multiplier);
290             nextVelocity.add((Tuple3d) normal);
291         }
292         if (sensorRightFront2) {
293             botToHitLocation = new Vector3d(rightfront2.getHitLocation().x - botself.getLocation().x, rightfront2.getHitLocation().y - botself.getLocation().y, 0);
294             multiplier = Math.pow((longRayLength2 - botToHitLocation.length()) * 2f / longRayLength2, forceOrder);
295             normal = rightfront2.getHitNormal();
296             if (treeCollisions && (!sensorLeftFront2 && !SteeringTools.pointIsLeftFromTheVector(actualVelocity, normal) ) ) {
297                 normal = computeTreeCollisionVector(normal);
298             }
299             if (frontCollisions && !sensorFront) {
300                 normal = computeFrontCollisionVector(normal);
301             }
302             normal.scale(SIDE_FRONT_RAY_WEIGHT * repulsiveForce * multiplier);
303             nextVelocity.add((Tuple3d) normal);
304         }*/
305 
306         //If nothing is signalling, the bot can return to its normal speed..
307         if (!sensor) {
308             wantsToGoFaster.setValue(true);
309         } else {
310             wantsToGoFaster.setValue(false);
311         }
312         
313         return nextVelocity;
314     }
315 
316     /**If it's really the front collision, the "mirror vector" is returned. Otherwise the unchanged parameter normal is returned.*/
317     private Vector3d computeTreeCollisionVector(Vector3d normal) {        
318         Vector3d av = botself.getVelocity().getVector3d();
319         /* Jestliže signalizuje pravý přední paprsek a ne levý přední -
320          * a navíc jde normálová síla v místě kolize stejným směrem jako jde paprsek, tedy doleva ==> pak by se neměla přičítat tato normála, ale spíše síla na durhou stranu.
321          * Značí to situaci, kdy jsme narazili na úzkou překážku (strom) a levý přední paprsek prošel levým krajem překážky.
322          * Bez tohoto ošetření by nás to stočilo doleva, což nechceme.*/
323         /* Pro pravou stranu je to naopak. *//* Jestliže signalizuje levý přední paprsek a ne pravý přední -
324          * a navíc jde normálová síla v místě kolize stejným směrem jako jde paprsek, tedy doleva ==> pak by se neměla přičítat tato normála, ale spíše síla na durhou stranu.
325          * Značí to situaci, kdy jsme narazili na úzkou překážku (strom) a levý přední paprsek prošel levým krajem překážky.
326          * Bez tohoto ošetření by nás to stočilo doleva, což nechceme.*/
327 
328         Vector2d start = new Vector2d(botself.getLocation().x, botself.getLocation().y);
329         Vector2d end = new Vector2d(start.x-av.x, start.y-av.y);
330         Vector2d point = new Vector2d(start.x + normal.x, start.y + normal.y);
331         Vector2d pata = SteeringTools.getNearestPoint(start, end, point, false);
332         Vector2d pointToPata = new Vector2d(pata.x - point.x, pata.y - point.y);
333         pointToPata.scale(2);
334         Vector2d mirrorPoint = new Vector2d(point.x + pointToPata.x, point.y + pointToPata.y);
335         
336         Vector3d result = new Vector3d(mirrorPoint.x - start.x, mirrorPoint.y - start.y, 0);
337         
338         if (SteeringManager.DEBUG) {
339             System.out.println("Obstacle avoidance tree collision. " + result.length());
340         }
341         return result;
342     }
343 
344     /**If it's really the front collision, the "mirror vector" is returned. Otherwise the unchanged parameter normal is returned.*/
345     private Vector3d computeFrontCollisionVector(Vector3d normal) {
346         Vector3d av = botself.getVelocity().getVector3d();
347         Vector3d result = new Vector3d(normal.x, normal.y, 0);
348         Vector3d negativeActual = new Vector3d(-av.x, -av.y, 0);
349 
350         if (SteeringManager.DEBUG) System.out.println("Angle "+SteeringTools.radiansToDegrees(normal.angle(negativeActual)));
351         if (result.angle(negativeActual) <= Math.PI/2) {
352             boolean turnLeft;
353             if (result.angle(negativeActual) == 0) {
354                 turnLeft = random.nextBoolean();
355             } else {
356                 turnLeft = SteeringTools.pointIsLeftFromTheVector(av, result);
357             }
358             Vector3d turn = SteeringTools.getTurningVector2(av, turnLeft);  //Tady se původně používal getTurningVector1.
359             turn.normalize();
360             turn.scale(0.5);    //Aby neměl rotační vektor tak velký vliv.
361             result.add(turn);
362             result.normalize();
363             if (SteeringManager.DEBUG) System.out.println("Obstacle avoidance front collision: turn left "+turnLeft);
364         }
365         return result;
366     }
367       
368     @Override
369     public void setProperties(SteeringProperties newProperties) {
370         this.repulsiveForce = ((ObstacleAvoidanceProperties)newProperties).getRepulsiveForce();
371         this.forceOrder = ((ObstacleAvoidanceProperties)newProperties).getForceOrder();
372         this.frontCollisions = ((ObstacleAvoidanceProperties)newProperties).isFrontCollisions();
373         this.treeCollisions = ((ObstacleAvoidanceProperties)newProperties).isTreeCollisions();
374     }
375     
376     public ObstacleAvoidanceProperties getProperties() {
377         ObstacleAvoidanceProperties properties = new ObstacleAvoidanceProperties();
378         properties.setRepulsiveForce(repulsiveForce);
379         properties.setForceOrder(forceOrder);
380         properties.setFrontCollisions(frontCollisions);
381         properties.setTreeCollisions(treeCollisions);
382         return properties;
383     }
384 }