Thursday, April 24, 2008 3:41 PM
by
PollRobots
Pursuit Camera Service (part II)
When I finished the pursuit camera service, I was unhappy with some aspects of it. Fixing them requires implementing the camera as an entity. Most importantly this allows me to update the position every frame and requires the camera to not be the main camera. As a nice consequence implementing this as an entity allows the camera to play well with the record and playback functionality.
Implementing a new camera entity is easy. I just create a new C# file in my existing Pursuit Camera project and add a new class which derives from CameraEntity.
namespace Robotics.PursuitCamera
{
[DataContract]
public class PursuitCameraEntity : CameraEntity
{
Configuration
To configure the entity it needs properties that define its behavior
string _targetName;
[DataMember, Browsable(true), Description("Name of the entity to track")]
public string TargetName
{
get
{
return _targetName;
}
set
{
if (value != _targetName)
{
_targetName = value;
FindTarget();
}
}
}
[DataMember, Browsable(true), Description("Minimum distance to keep the camera from the entity")]
public float MinDistance { get; set; }
[DataMember, Browsable(true), Description("Maximum distance to keep the camera from the entity")]
public float MaxDistance { get; set; }
[DataMember, Browsable(true), Description("Height above ground plane to keep the camera")]
public float Altitude { get; set; }
These properties clearly define the essential minimum information that the entity needs to track a target. The name of the target is, as before, the entity name to target, the other properties should be fairly obvious.
Note: the setter for TargetName calls a method FindTarget(), I'll come back to that when I discuss the initialization code.
Constructing the Entity
Simulation entities typically get constructed in one of two scenarios.
- A simulation scene is being loaded from file and deserialized. In this case a default constructor is called and all the members are populated by the deserialization code.
- The entity is being created from the New Entity UI within the simulation window. In this case a parameterized constructor can be called, giving the option of presenting the user with critical configuration choices.
Knowing this, I implement two constructors, and a method to populate all the configuration with sensible default values.
public PursuitCameraEntity()
{
SetDefaults();
}
public PursuitCameraEntity(string target)
: base()
{
_targetName = target;
SetDefaults();
}
private void SetDefaults()
{
MinDistance = 4;
MaxDistance = 6;
Altitude = 2;
OcclusionThreshold = 0.5f;
}
Initializing
Regardless of how the entity is created, it will be initialized before it is used. I use this as the opportunity to find the target entity, and to validate the camera type.
public override void Initialize(xna.Graphics.GraphicsDevice device, PhysicsEngine physicsEngine)
{
base.Initialize(device, physicsEngine);
if (CameraModel != CameraModelType.FirstPerson)
{
CameraModel = CameraModelType.FirstPerson;
}
FindTarget();
}
private void FindTarget()
{
if (HasBeenInitialized)
{
var query = new VisualEntity();
query.State.Name = _targetName;
Activate(
Arbiter.Choice(
SimulationEngine.GlobalInstancePort.Query(query),
success => Target = success.Entity,
CcrServiceBase.EmptyHandler
)
);
}
}
public VisualEntity Target;
void Activate<T>(params T[] tasks)
where T : ITask
{
SimulationEngine.GlobalInstance.Activate(tasks);
}
So, after calling the base Initialize() method, I check that the camera is a first person camera type (which simplifies managing the viewing parameters of the camera) and call FindTarget(), which allows me to post a query to the SimulationEngine that looks for an entity with the name _targetName. If an entity is found then the Target field is set appropriately
Note: querying the simulation engine is an asynchronous process, so I will need to be careful to check that it has completed successfully before I rely on any result.
Tracking the target object
The only thing that remains is to implement the tracking logic on each frame. The Update() method of each VisualEntity is called once per frame. So all I need to do is override that method, and implement the same logic as I did in the service implementation.
public override void Update(FrameUpdate update)
{
if (Target != null)
{
// vector to the camera, used for computing the new position
var entityToCamera = Location - Target.Position;
var distance = entityToCamera.Length();
if (distance > 0.1)
{
float scale = 1;
// scale the vector if the distance is outside the bounds
if (distance > MaxDistance)
{
scale = MaxDistance / distance;
}
else if (distance < MinDistance)
{
scale = MinDistance / distance;
}
var scaled = xna.Vector3.Multiply(entityToCamera, scale);
// set the new camera position, although the altitude will be off.
var newCamera = Target.Position + scaled;
// constrain the altitude.
newCamera.Y = Altitude;
base.SetViewParameters(newCamera, Target.Position);
}
}
base.Update(update);
}
Using the entity
That's all the coding needed to implement this as a simulation entity. Now all that remains is to add this entity to a scene. To do this:
- Start a simulation manifest of your choice, as before I'm just using
samples\Config\MobileRobots.P3DX.Simulation.manifest.xml
- Put the simulator into Edit Mode, by selecting Edit from the Mode menu.
- Select New... from the Entity menu
- In the New Entity dialog find PursuitCameraEntity in the EntityType list and select it, click OK
- When prompted for the target name enter
P3DXMotorBase, and click OK
- Set the simulator back to Run Mode (Mode -> Run)
- Select the new PursuitCamera from the Cameras menu.
- Drive the robot around and see the camera following it.
- From the File menu, select Save Scene As... to create a new manifest and scene file that now contain the pursuit camera entity
Next steps
Only two things really remain at this point
- I'd like to be able to control the entity from a service. This will allow me to dynamically change which object I'm tracking and, by changing the distance, altitude and field of view values, zoom in and out on the target
- I still want to do occlusion detection so that the camera can do its best to ensure a clear line of sight to the target entity