Trajectory Pipeline
The architecture of the trajectory pipeline is illustrated below:
Fig. 48 Trajectory pipeline schematic.
Point Generating Sources
The MPointGeneratorSource generates a list of points packed into a
MPoints data item, managed in memory. It requests a
MPointGeneratorInterface to generate the points. The horizontal
cross-section and vertical cross-section actors implement this interface
and provide the necessary methods for point generation based on the
actor’s current state and user interface.
To implement a custom point source, have the class inherit from
MPointGeneratorInterface and implement the following abstract methods:
QString getPointGeneratingSourceBaseRequestKeys(): Generates a clear, identifiable string based on its current state. The string has to fully define the state required to uniquely identify the points being generated.MPoints* generatePointsFromString(const QString&): Generates the data item containing the list of points based on the provided request string. This method must always return the same set of points given the same input string.
Register the interface with MPointSourceFactory in the constructor, e.g.:
MPointSourceFactory::getInstance()->registerPointGeneratingInterface<MPointGeneratorInterfaceSettings>(this);
The template argument must be a class inheriting from
MPointGeneratorInterfaceSettings, containing the user interface settings.
Remember to deregister the interface upon deletion. Use
MPointSourceFactory::showCreationDialog() to display a dialog
allowing the user to select a point generation interface from the registered ones.
Point Filters
Point filters inherit from MPointFilter and accept a MPoints data
item as input, returning an altered MPoints data item. Each filter
has a MPointSource as its parent, which may be either a
MPointGeneratorSource or another MPointFilter; filters are therefore
chained. Adding a new point filter follows the same three-step pattern used by
trajectory filters below: a filter class, a settings class, and registration
to the factory.
1. The filter (inherits MPointFilter). You need to implement:
QString getFilterName() override– Returns the general name identifier for the filter. Each filter type needs to have a unique identifier. It is used for saving the pipeline state to configuration files, and restoring the trajectory pipeline when a session is loaded.MPoints *produceData(MDataRequest request)– the filtering heuristic. Read the filter’s state from the request via its request keys and return the filteredMPoints.MTask *createTaskGraph(MDataRequest request)– declare the dependency on the parent source (and any grid sources it samples) so the scheduler runs them first.
MThresholdPointFilter::MThresholdPointFilter()
: MPointFilter({"POINT_FILTER_THRESHOLD"}) // base request key
{}
// in the header:
static QString filterName() { return "Threshold filter"; }
2. The settings (inherits MPointFilterProperties) own the filter’s user
interface and translate it into the request. Implement the constructor, add your properties
(and, if the filter samples a data field, the actor-variable hooks
onAddActorVariable / onChangeActorVariable / onDeleteActorVariable)
to the actor – and
void insertCurrentStateToRequest(MDataRequestHelper &rh,
const QStringList &keys) override;
which writes the current UI state into keys (the filter’s unique request
keys).
3. Register the pair in the MPointFilterFactory constructor:
registerPointFilter<MThresholdPointFilter, MThresholdPointFilterProperties>();
The “Add point filter” button uses the factory to let
the user pick a registered filter, builds its settings, and inserts the filter
into every pipeline. See MThresholdPointFilter (filter by an upper/lower
data threshold) and MBoundingBoxPointFilter for complete examples.
Trajectory Sources
Precomputed trajectories are loaded via MTrajectoryReader, while
on-the-fly computed trajectories are generated through
MTrajectoryComputationSource. The computation process is encapsulated
in the MTrajectoryComputation class. Trajectory sources return a
MTrajectories data item, containing the vertex data of the trajectories
along with some metadata.
Trajectory Filters
As shown in the schematic above, trajectory filters are connected downstream
of the trajectory source. A MTrajectoryFilter reads both the trajectories
(MTrajectoryDataSource) and the incoming selection
(MTrajectorySelectionSource) and returns a new selection. Filters can
therefore
discard entire trajectories, or
discard segments of trajectories,
without copying the (potentially large) vertex data. They only narrow the set
of selected indices into it. Filters are chained; each filter’s input is the
previous filter’s selection. Currently implemented filters are the
ascent/descent (MPressureTimeTrajectoryFilter), bounding-box
(MBoundingBoxTrajectoryFilter) and thin-out (MThinOutTrajectoryFilter)
filters.
A new trajectory filter follows the same three steps as a point filter.
Create a filter inheriting from MTrajectoryFilter that holds the filtering
logic, a MTrajectoryFilterProperties defining the user facing properties, and
register the pair to the MTrajectoryFilterFactory.
Note
Request keys are unique per instance, not per type.
A filter passes base keys (e.g. "TRAJ_FILTER_BBOX") to its
constructor. The base class appends the filter’s runtime id
(key + "_" + getID()) to build the actual keys returned by
locallyRequiredKeys(). This is why several instances of the same
filter type can coexist (and be chained) in one pipeline without their
request keys colliding.
Persisting configured filters
Point and trajectory filters are configured at runtime, so the trajectory actor
must be able to recreate them when a session is loaded. On save
(MTrajectoryActor::saveTrajectoryPipelineConfig), the actor writes the
number of configured filters and, for each, its getFilterName() under an
indexed prefix (trajectoryFilter0, trajectoryFilter1, …); the filter’s
own property group is assigned that prefix, so the property framework serializes
its settings there. On load, the actor reads the name back, recreates the
settings through the factory
(MTrajectoryFilterFactory::createProperties(name, actor)) and calls
addTrajectoryFilter again.
Single Point Filter
The MSingleTimeTrajectoryFilter extracts single time position data from
the trajectories, used for rendering spheres at points in time selected in
the user interface. It returns a MWritableTrajectorySelection, but
specifically includes only subsets of trajectories with length 1.
Normals Source
The MTrajectoryNormalsSource computes normal directions along all
trajectories, essential for accurate rendering and shading. The returned
data item is a MTrajectoryNormals.
Note
Normal directions depend on the vertical scale of the scene. Thus,
normals must be computed separately for each scene, resulting
in an additional layer of lists within the MTrajectoryNormals
data item: normals per scene view.
Auxiliary Data Sources
Beyond the selection, a pipeline can attach supplemental data to the
trajectory collection, computed either per vertex or per whole trajectory. All
such sources inherit from MTrajectorySupplementDataSource (which holds a
trajectory source and a selection source) and produce a subclass of
MSupplementalTrajectoryData tagged with a MSupplementalTrajectoryDataType
(e.g. DATA_PER_VERTEX, NORMALS). Existing supplements:
Per vertex –
MFloatPerTrajectoryVertexSourceprovides one float per vertex.MAuxiliaryVariableTrajectorySourceuses it to sample an auxiliary data field along each trajectory (used to colour trajectories by that field).Per trajectory –
MFloatPerTrajectorySourceprovides one float per trajectory.MDeltaPressurePerTrajectorySourceuses it to compute the maximum pressure change along a trajectory within a time interval.The normals source (see below) is also a supplement, tagged
NORMALS.
To add a supplement, subclass MTrajectorySupplementDataSource, implement
produceData / createTaskGraph to compute your
MSupplementalTrajectoryData subclass, and give it the correct
MSupplementalTrajectoryDataType. Register the source with the pipeline
manager, which keeps a list of supplement sources: its produceData then
routes NORMALS and DATA_PER_VERTEX into their dedicated collection slots
(dynamic_lease_cast to the concrete type) and gathers every other type into
the generic supplement list, from which a consumer can retrieve it via the
MTrajectoryDataCollection.
The Trajectory Pipeline Barrier
The MTrajectoryPipelineManager has dual responsibilities: it owns the
pipeline structure – the trajectory source, the filter chain, the single-time
filter, the normals source and the supplement sources – and it acts as the
barrier that gathers their outputs.
Pipeline integration. Filters are added and removed at runtime via
addTrajectoryFilter / removeTrajectoryFilter (and the point-filter
equivalents). After any change, updatePipeline() updates the chain so that
each enabled filter’s input is the previous enabled filter’s output; disabled
filters are skipped. Because the dispatcher owns one pipeline per point
source, adding a filter adds a corresponding instance to every pipeline. Having
one dedicated pipeline per point source allows us to re-use cached trajectories
when only one of many point sources is changed (e.g., a pole is moved).
The barrier. In produceData() the manager performs no computation
itself. It requests data from each upstream source – the trajectories, the
selection from the last enabled filter, the single-time selection, the normals
and every supplement – and packs them into a single
MTrajectoryDataCollection, so a requester is notified only once all parts
are available.
Note
Lifetime of the upstream trajectory data.
MTrajectoryDataCollection holds no copies of the trajectory data;
instead it stores an MDataLease for each upstream item (trajectories,
selection, normals, supplements, …). A lease pins its item in the memory
manager for as long as the lease exists (RAII, similar to shared_ptr),
so the whole set of upstream items stays alive exactly as long as the
collection does. When the last lease on the collection is released and the
collection is evicted, its member leases are destroyed and the upstream
items become evictable again.
The Trajectory Dispatcher
The MTrajectoryRequestDispatcher manages the trajectory pipelines. Each
point source is linked to a dedicated pipeline, allowing efficient caching
of computation results. When a point source changes, only the points for that
source are used to recompute the corresponding trajectories.
Similar to the pipeline, it serves as a barrier, holding references to both the pipeline and the associated data item references to collections returned by the pipelines.