View Javadoc

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