Help with "Programming Game AI by Example"
I'm fairly sure there is a simple solution to the probelm I'm having. I want to make the steering behaviors take the maxTurnRate into account. Right now when I use the Seek behavior my vehicle reaches the target and does a 180 degree flip if the target ends up behind it.
I'm still learning the ins and outs of the math so I'm not sure how to fix the problem. Although I'm guessing it should be something like the code in the RotateHeadingToFacePosition method.
Any help you can give would be great. Thanks in advance.
For those of us without the book, what code are you using to control the agent's movement? You should be able to clamp the angular velocity to the maximum amount.
Here is the code that works without behaviors
Here is the code that uses the steering behaviors. The line
SteeringForce = steering.Calculate()
returns a Vector2D with the steering force to be applied to the vehicle(sprite). I think I should tweak the returned value somehow but I'm not sure how to go about it.
Rem bbdoc: Face a target. about: given a target position, this Method rotates the entity's heading and side vectors by an amount Not greater than m_dMaxTurnRate Until it directly faces the target. returns True when the heading is facing in the desired direction EndRem Method RotateHeadingToFacePosition:Byte(target:Vector2D) Local toTarget:Vector2D = SubtractVector(target, position) toTarget.Normalize() Local dot:Double = heading.DOT(toTarget); '//some compilers lose acurracy so the value is clamped To ensure it '//remains valid For the ACos 'Clamp(dot, -1, 1); '//first determine the angle between the heading vector And the target Local angle:Double = ACos(dot) '//Return True If the player is facing the target If angle < 0.00001 Then Return True '//clamp the amount To turn To the Max turn rate If angle > maxTurnRate Then angle = maxTurnRate; '//The Next few lines use a rotation matrix To rotate the player's heading '//vector accordingly Local RotationMatrix:C2DMatrix = New C2DMatrix ' TODO: '//notice how the direction of rotation has To be determined when creating '//the rotation matrix RotationMatrix.Rotate(angle * heading.Sign(toTarget)) RotationMatrix.TransformVector2D(heading) RotationMatrix.TransformVector2D(velocity) '//finally recreate m_vSide side = heading.CreateRightNormal() Return False End Method
Here is the code that uses the steering behaviors. The line
SteeringForce = steering.Calculate()
returns a Vector2D with the steering force to be applied to the vehicle(sprite). I think I should tweak the returned value somehow but I'm not sure how to go about it.
Rem bbdoc: Updates the vehicle's position from a series of steering behaviors EndRem Method Update(time_elapsed:Double) 'DebugLog "time_elapsed="+time_elapsed '//update the time elapsed timeElapsed = time_elapsed; '//keep a record of its old position so we can update its cell later '//in this Method Local OldPos:Vector2D = GetPosition(); Local SteeringForce:Vector2D = Null '//calculate the combined force from each steering behavior in the '//vehicle's list SteeringForce = steeringb.Calculate(); 'DebugLog "sf.x="+SteeringForce.X + " sf.y="+SteeringForce.Y+" sf.l="+SteeringForce.length() '-------------------------------------- ' I THINK THE CHANGE SHOUDL HAPPEN HERE '-------------------------------------- '//Acceleration = Force/Mass Local acceleration:Vector2D = SteeringForce.divide(mass) 'DebugLog "a.x="+acceleration.X + " a.y="+acceleration.Y+" a.l="+acceleration.length() '//update velocity velocity.Add(acceleration.Multiply(time_elapsed)) '//make sure vehicle does Not exceed maximum velocity velocity.Truncate(maxSpeed); 'DebugLog "v.x="+velocity.X + " v.y="+velocity.Y+" v.l="+velocity.length() '//update the position position.Add(velocity.MultiplyCopy(time_elapsed)) '//update the heading If the vehicle has a non zero velocity If velocity.LengthSquared() > 0.00000001 Then heading = Vec2DNormalize(velocity); side = heading.CreateRightNormal(); EndIf '//EnforceNonPenetrationConstraint(this, World()->Agents()); '//treat the screen as a toroid 'WrapAround(m_vPos, m_pWorld->cxClient(), m_pWorld->cyClient()); '//update the vehicle's current cell if space partitioning is turned on If steeringb.IsSpacePartitioningOn() Then'TODO gworld.CellSpace().UpdateEntity(Self, OldPos); EndIf If IsSmoothingOn() Then smoothedHeading = headingSmoother.Update(heading) EndIf End Method
Mat is helping me out with this at:
http://ai-junkie.com/board/viewtopic.php?t=197
http://ai-junkie.com/board/viewtopic.php?t=197
*mod hat on*
Please refrain from posting code supplied with a book on this website, unless you specifically have the copyright holders permission (in this case, Mat's publisher, or Mat himself if they returned copyright to him). Posting here would be construed as public dissemination of copyrighted material, which violates the rights of the copyright holder and leaves GD.net open to prosecution.
*mod hat off*
Cheers,
Timkin
Please refrain from posting code supplied with a book on this website, unless you specifically have the copyright holders permission (in this case, Mat's publisher, or Mat himself if they returned copyright to him). Posting here would be construed as public dissemination of copyrighted material, which violates the rights of the copyright holder and leaves GD.net open to prosecution.
*mod hat off*
Cheers,
Timkin
Quote: Original post by Timkin
Please refrain from posting code supplied with a book on this website, unless you specifically have the copyright holders permission (in this case, Mat's publisher, or Mat himself if they returned copyright to him). Posting here would be construed as public dissemination of copyrighted material, which violates the rights of the copyright holder and leaves GD.net open to prosecution.
After doing a little searching the code appears to be freely available online here, in C++.
@zparticle2: The most direct way to limit the angular velocity would be to find the angle between the current velocity and the new velocity, and if that exceeds the maximum turn rate you find a new acceleration that satisfies the turn rate constraint. This is something I came up with off the top of my head.
// Helper functionsVector2D lerp(const Vector2D& a, const Vector2D& b, float t){ return (1 - t) * a + t * b;}Vector2D slerp(const Vector2D& a, const Vector2D& b, float t){ // precondition: a and b are on a circle of radius r float s = acos(a.Dot(b) / (a.Length() * b.Length())); return a*sin((1-t)*s)/sin(s) + b*sin(t*s)/sin(s);}// ... // inside Update(...)SteeringForce = m_pSteering->Calculate();Vector2D acceleration = SteeringForce / m_dMass;Vector2D NewVelocity = m_vVelocity + acceleration; float angle = acos(m_vVelocity.Dot(NewVelocity) / (m_vVelocity.Length() * NewVelocity.Length())); // Check against max turn rate (in radians/sec)if(angle > MaxTurnRate){ // Perform some clever interpolations to get an acceleration that satisfies // the maximum turn rate Vector2D OldV2 = Vec2DNormalize(NewVelocity) * m_vVelocity.Length(); Vector2D NewV1 = Vec2DNormalize(m_vVelocity) * NewVelocity.Length(); float t = MaxTurnRate / angle; Vector2D MaxV1 = slerp(m_vVelocity, OldV2, t); Vector2D MaxV2 = slerp(NewV1, NewVelocity, t); acceleration = lerp(MaxV1, MaxV2, t) - m_vVelocity;} m_vVelocity += acceleration * time_elapsed; // continue as before
It's untested, but the diagrams I drew looked promising [smile]
@Timkin: Sorry, the code is publicly available though. Also the code I posted is a port of the code from the books C++ to BlitzMax.
@Zipster: Thanks for the help, here is Mats response to me
unfortunately this didn't work or more likely I did it wrong. The vehicle barely moved at all and would never turn.
I've added your code but the program goes crazy. The calculation of the angle causes ACos to return an value that is infinite (or maybe it's just a NAN). Any ideas? I tried clamping the resulting value between -1 and 1 which worked but the vehicle then behaves just as it always has. Thanks again for your help.
@Zipster: Thanks for the help, here is Mats response to me
Quote:
the velocity vector of the vehicle in this timestep (vNow) and the the velocity in the previous (vPrev) can be thought of as two hands on a watch. To constrain the angle between the vectors by maxTurnRate follow these steps:
speed = the length of vNow
normPrev = Normilize(vPrev)
amountOfTurn = calculate angle between VNow and vPrev
Rotate normPrev about the origin by the smaller of maxTurnRate or amountOfTurn
multiply normPrev by speed
However, some of the steering behaviors may then perform less well since they are not taking maxTurnRate into consideration. Arrive, in particular, may have difficulty stopping.
unfortunately this didn't work or more likely I did it wrong. The vehicle barely moved at all and would never turn.
I've added your code but the program goes crazy. The calculation of the angle causes ACos to return an value that is infinite (or maybe it's just a NAN). Any ideas? I tried clamping the resulting value between -1 and 1 which worked but the vehicle then behaves just as it always has. Thanks again for your help.
Mat's code uses a vehicle model that is based on a point mass approximation. It's very simple and computationally cheap, but not very realistic since point masses don't exist in the real world. The simulation is slightly improved by assuming velocity alignment (where the heading of the vehicle is the direction of velocity), but that also has problems. For instance, vehicles can turn when their speed is zero and the angular velocity is theoretically unbounded. I recommend the article Steering Behaviors For Autonomous Character by Craig Reynolds which goes into more detail.
If you want a model that is more consistent with real vehicles then I would suggest a heading/speed representation, which incorporates an object's radius and moment of inertia. The behavioral control doesn't need to be changed, because as the article above suggests the steering behavior can be made independent of the locomotion scheme given the right control signal. In this representation, the control signal is the desired velocity. This is different from the steering force in that you don't subtract the current velocity. Once you have the desired velocity, you determine the angle between your heading and the desired velocity. You can then apply both torque and force to the object. The torque causes it to turn, and the force is applied in the direction of the heading so that the vehicle can move. Since the object has both speed and angular velocity, you can enforce a max speed and max turn rate.
However in order for this approach to work well you need a few well-designed control laws that govern the amount of torque and force required. It's not as simple as just dividing a steering force vector by mass and adding it to velocity, since the direction of movement is always in the direction of the heading (as opposed to being arbitrary), and the heading itself needs to be controlled. Without going into too much detail on the control theory behind it, I've used the following equations quite successfully:
F = Kvfm(Vd - V)
τ = I(Kp(θd - θ) - Kvτω)
Where F is force, τ is torque, m is mass, I is moment of inertia, Vd is the desired speed (scalar), V is the current speed (scalar), θd is the desired heading (radians), θ is the current heading (radians), ω is the current angular velocity (radians/sec), Kvf is 2.0 (constant), Kp is 16.0 (constant), and Kvτ is 8.0 (constant). These constants can be modified to change the locomotion behavior, but right now they're calibrated for a 0.5 transient response time and no heading oscillation or overshoot.
If you want a model that is more consistent with real vehicles then I would suggest a heading/speed representation, which incorporates an object's radius and moment of inertia. The behavioral control doesn't need to be changed, because as the article above suggests the steering behavior can be made independent of the locomotion scheme given the right control signal. In this representation, the control signal is the desired velocity. This is different from the steering force in that you don't subtract the current velocity. Once you have the desired velocity, you determine the angle between your heading and the desired velocity. You can then apply both torque and force to the object. The torque causes it to turn, and the force is applied in the direction of the heading so that the vehicle can move. Since the object has both speed and angular velocity, you can enforce a max speed and max turn rate.
However in order for this approach to work well you need a few well-designed control laws that govern the amount of torque and force required. It's not as simple as just dividing a steering force vector by mass and adding it to velocity, since the direction of movement is always in the direction of the heading (as opposed to being arbitrary), and the heading itself needs to be controlled. Without going into too much detail on the control theory behind it, I've used the following equations quite successfully:
F = Kvfm(Vd - V)
τ = I(Kp(θd - θ) - Kvτω)
Where F is force, τ is torque, m is mass, I is moment of inertia, Vd is the desired speed (scalar), V is the current speed (scalar), θd is the desired heading (radians), θ is the current heading (radians), ω is the current angular velocity (radians/sec), Kvf is 2.0 (constant), Kp is 16.0 (constant), and Kvτ is 8.0 (constant). These constants can be modified to change the locomotion behavior, but right now they're calibrated for a 0.5 transient response time and no heading oscillation or overshoot.
On the posting code issue... there are a few sub issues to consider... for instance... just because it's posted on the web, doesn't mean you have the right to repost it... and just because its a port, doesn't mean you had permission to port it in the first place.
However, if the code can reasonably be considered public domain (perhaps because of a statement by the author or publisher that you may freely distribute it provided the copyright notice remains in tact) then the best way to acknolwedge the author/copyright holder is to recognise your source of the information. So, if the code is publicly available online, then simply include a URL for the page from which the code is accessible (where you found it). If you've ported the code, then you should also note the origin of the source and note that, if the code is not public domain, then you had permission to port it. This is fairly trivial to do and in this day and age is a fairly smart thing to do.
Utimately, I'm not trying to curb anyone from posting code (unless its stolen!)... I just want to make sure that we're all honest about its source and recognise the hard work of others, where that recognition is due (and protect ourselves from claims that we have breached copyright).
Cheers,
Timkin
However, if the code can reasonably be considered public domain (perhaps because of a statement by the author or publisher that you may freely distribute it provided the copyright notice remains in tact) then the best way to acknolwedge the author/copyright holder is to recognise your source of the information. So, if the code is publicly available online, then simply include a URL for the page from which the code is accessible (where you found it). If you've ported the code, then you should also note the origin of the source and note that, if the code is not public domain, then you had permission to port it. This is fairly trivial to do and in this day and age is a fairly smart thing to do.
Utimately, I'm not trying to curb anyone from posting code (unless its stolen!)... I just want to make sure that we're all honest about its source and recognise the hard work of others, where that recognition is due (and protect ourselves from claims that we have breached copyright).
Cheers,
Timkin
Quote: Original post by Zipster
These constants can be modified to change the locomotion behavior, but right now they're calibrated for a 0.5 transient response time and no heading oscillation or overshoot.
What was the settling time? No overshoot and no transient oscillation suggests an asymptotic convergence on the target, which is not desirable if your gains generate large settling times. Permitting 5% overshoot and ensuring moderate damping (0.3-0.7) would usually give better performance (if I find time later today I might play around with Simulink and see what I can come up with).
Cheers,
Timkin
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement