diff --git a/applications/utilities/postProcessing/lagrangian/particleTracks/createFields.H b/applications/utilities/postProcessing/lagrangian/particleTracks/createFields.H
index 742fe261d2f6411e6323295a1e6d9fb57ffae4ad..36186a9e92c846f7aa3321b152220cdd8bc1956d 100644
--- a/applications/utilities/postProcessing/lagrangian/particleTracks/createFields.H
+++ b/applications/utilities/postProcessing/lagrangian/particleTracks/createFields.H
@@ -15,4 +15,12 @@ const label sampleFrequency(propsDict.get<label>("sampleFrequency"));
 
 const label maxPositions(propsDict.get<label>("maxPositions"));
 
+const label maxTracks(propsDict.getOrDefault<label>("maxTracks", -1));
+
 const word setFormat(propsDict.getOrDefault<word>("setFormat", "vtk"));
+
+const wordRes fieldNames(propsDict.getOrDefault<wordRes>("fields", wordRes()));
+
+const word UName(propsDict.getOrDefault<word>("U", "U"));
+
+const dictionary formatOptions = propsDict.subOrEmptyDict("formatOptions");
diff --git a/applications/utilities/postProcessing/lagrangian/particleTracks/particleTracks.C b/applications/utilities/postProcessing/lagrangian/particleTracks/particleTracks.C
index e247d87021bf24936553544bc04016602a79f23f..c66fe1995fc33174d23d35cbf2de11b1595983fd 100644
--- a/applications/utilities/postProcessing/lagrangian/particleTracks/particleTracks.C
+++ b/applications/utilities/postProcessing/lagrangian/particleTracks/particleTracks.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,8 +31,8 @@ Group
     grpPostProcessingUtilities
 
 Description
-    Generate a legacy VTK file of particle tracks for cases that were
-    computed using a tracked-parcel-type cloud.
+    Generate particle tracks for cases that were computed using a
+    tracked-parcel-type cloud.
 
 \*---------------------------------------------------------------------------*/
 
@@ -44,16 +45,206 @@ Description
 #include "OFstream.H"
 #include "passiveParticleCloud.H"
 #include "writer.H"
+#include "ListOps.H"
+
+#define createTrack(field, trackValues)                                        \
+createTrackField                                                               \
+(                                                                              \
+    field,                                                                     \
+    sampleFrequency,                                                           \
+    maxPositions,                                                              \
+    startIds,                                                                  \
+    allOrigProcs,                                                              \
+    allOrigIds,                                                                \
+    trackValues                                                                \
+);
+
+#define setFields(fields, fieldNames)                                          \
+setTrackFields                                                                 \
+(                                                                              \
+    obr,                                                                       \
+    fields,                                                                    \
+    fieldNames,                                                                \
+    nTracks,                                                                   \
+    startIds,                                                                  \
+    allOrigProcs,                                                              \
+    allOrigIds,                                                                \
+    maxPositions,                                                              \
+    sampleFrequency                                                            \
+);
+
+#define writeFields(fields, fieldNames, tracks, times, dirs)                   \
+writeTrackFields                                                               \
+(                                                                              \
+    fields,                                                                    \
+    fieldNames,                                                                \
+    tracks,                                                                    \
+    times,                                                                     \
+    dirs,                                                                      \
+    setFormat,                                                                 \
+    formatOptions,                                                             \
+    cloudName                                                                  \
+);
 
 using namespace Foam;
 
+template<class Type>
+void createTrackField
+(
+    const Field<Type>& values,
+    const label sampleFrequency,
+    const label maxPositions,
+    const labelList& startIds,
+    const List<labelField>& allOrigProcs,
+    const List<labelField>& allOrigIds,
+    List<DynamicList<Type>>& trackValues
+)
+{
+    List<Field<Type>> procField(Pstream::nProcs());
+    procField[Pstream::myProcNo()] = values;
+    Pstream::gatherList(procField);
+
+    if (!Pstream::master())
+    {
+        return;
+    }
+
+    const label nTracks = trackValues.size();
+
+    forAll(procField, proci)
+    {
+        forAll(procField[proci], i)
+        {
+            const label globalId =
+                startIds[allOrigProcs[proci][i]] + allOrigIds[proci][i];
+
+            if (globalId % sampleFrequency == 0)
+            {
+                const label trackId = globalId/sampleFrequency;
+
+                if
+                (
+                    trackId < nTracks
+                 && trackValues[trackId].size() < maxPositions
+                )
+                {
+                    trackValues[trackId].append(procField[proci][i]);
+                }
+            }
+        }
+    }
+}
+
+
+template<class Type>
+void writeTrackFields
+(
+    List<List<DynamicList<Type>>>& fieldValues,
+    const wordList& fieldNames,
+    const PtrList<coordSet>& tracks,
+    const List<scalarField>& times,
+    const List<vectorField>& dirs,
+    const word& setFormat,
+    const dictionary& formatOptions,
+    const word& cloudName
+)
+{
+    if (fieldValues.empty())
+    {
+        return;
+    }
+
+    auto writerPtr = writer<Type>::New(setFormat, formatOptions);
+
+    const fileName outFile(writerPtr().getFileName(tracks[0], wordList(0)));
+
+    const fileName outPath
+    (
+        functionObject::outputPrefix/cloud::prefix/cloudName/"particleTracks"
+    );
+    mkDir(outPath);
+
+    OFstream os(outPath/(pTraits<Type>::typeName & "tracks." + outFile.ext()));
+
+    Info<< "Writing " << pTraits<Type>::typeName << " particle tracks in "
+        << setFormat << " format to " << os.name() << endl;
+
+
+    List<List<Field<Type>>> fields(fieldValues.size());
+    forAll(fields, fieldi)
+    {
+        fields[fieldi].setSize(fieldValues[fieldi].size());
+        forAll(fields[fieldi], tracki)
+        {
+            fields[fieldi][tracki].transfer(fieldValues[fieldi][tracki]);
+        }
+    }
+
+    writerPtr().write(true, times, tracks, fieldNames, fields, os);
+}
+
+
+template<class Type>
+Foam::label setTrackFields
+(
+    const objectRegistry& obr,
+    List<List<DynamicList<Type>>>& fields,
+    List<word>& fieldNames,
+    const label nTracks,
+    const labelList& startIds,
+    const List<labelField>& allOrigProcs,
+    const List<labelField>& allOrigIds,
+    const label maxPositions,
+    const label sampleFrequency
+)
+{
+    const auto availableFieldPtrs = obr.lookupClass<IOField<Type>>();
+
+    fieldNames = availableFieldPtrs.toc();
+
+    if (Pstream::parRun())
+    {
+        Pstream::combineGather(fieldNames, ListOps::uniqueEqOp<word>());
+        Pstream::combineScatter(fieldNames);
+        Foam::sort(fieldNames);
+    }
+
+    const label nFields = fieldNames.size();
+
+    if (fields.empty())
+    {
+        fields.setSize(nFields);
+        fieldNames.setSize(nFields);
+        forAll(fields, i)
+        {
+            fields[i].setSize(nTracks);
+        }
+    }
+
+    forAll(fieldNames, fieldi)
+    {
+        const word& fieldName = fieldNames[fieldi];
+
+        const auto* fldPtr = obr.cfindObject<IOField<Type>>(fieldName);
+
+        createTrack
+        (
+            fldPtr ? static_cast<const Field<Type>>(*fldPtr) : Field<Type>(),
+            fields[fieldi]
+        );
+    }
+
+    return nFields;
+}
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 int main(int argc, char *argv[])
 {
     argList::addNote
     (
-        "Generate a legacy VTK file of particle tracks for cases that were"
+        "Generate a file of particle tracks for cases that were"
         " computed using a tracked-parcel-type cloud"
     );
 
@@ -76,9 +267,9 @@ int main(int argc, char *argv[])
         << nl << endl;
 
     labelList maxIds(Pstream::nProcs(), -1);
-    forAll(timeDirs, timeI)
+    forAll(timeDirs, timei)
     {
-        runTime.setTime(timeDirs[timeI], timeI);
+        runTime.setTime(timeDirs[timei], timei);
         Info<< "Time = " << runTime.timeName() << endl;
 
         Info<< "    Reading particle positions" << endl;
@@ -92,6 +283,8 @@ int main(int argc, char *argv[])
             const label origId = p.origId();
             const label origProc = p.origProc();
 
+            // Handle case where we are processing particle data generated in
+            // parallel using more cores than when running this application.
             if (origProc >= maxIds.size())
             {
                 maxIds.setSize(origProc+1, -1);
@@ -124,7 +317,7 @@ int main(int argc, char *argv[])
 
     // Calculate starting ids for particles on each processor
     labelList startIds(numIds.size(), Zero);
-    for (label i = 0; i < numIds.size()-1; i++)
+    for (label i = 0; i < numIds.size()-1; ++i)
     {
         startIds[i+1] += startIds[i] + numIds[i];
     }
@@ -132,130 +325,135 @@ int main(int argc, char *argv[])
 
 
     // Number of tracks to generate
-    label nTracks = nParticle/sampleFrequency;
+    const label nTracks =
+        maxTracks > 0
+      ? min(nParticle/sampleFrequency, maxTracks)
+      : nParticle/sampleFrequency;
 
     // Storage for all particle tracks
     List<DynamicList<vector>> allTracks(nTracks);
+    List<DynamicList<scalar>> allTrackTimes(nTracks);
+
+    // Lists of field, tracki, trackValues
+    //List<List<DynamicList<label>>> labelFields;
+    List<List<DynamicList<scalar>>> scalarFields;
+    List<List<DynamicList<vector>>> vectorFields;
+    List<List<DynamicList<sphericalTensor>>> sphTensorFields;
+    List<List<DynamicList<symmTensor>>> symTensorFields;
+    List<List<DynamicList<tensor>>> tensorFields;
+    //List<word> labelFieldNames;
+    List<word> scalarFieldNames;
+    List<word> vectorFieldNames;
+    List<word> sphTensorFieldNames;
+    List<word> symTensorFieldNames;
+    List<word> tensorFieldNames;
 
     Info<< "\nGenerating " << nTracks << " particle tracks for cloud "
         << cloudName << nl << endl;
 
-    forAll(timeDirs, timeI)
+    forAll(timeDirs, timei)
     {
-        runTime.setTime(timeDirs[timeI], timeI);
+        runTime.setTime(timeDirs[timei], timei);
         Info<< "Time = " << runTime.timeName() << endl;
 
-        List<pointField> allPositions(Pstream::nProcs());
-        List<labelField> allOrigIds(Pstream::nProcs());
-        List<labelField> allOrigProcs(Pstream::nProcs());
-
         // Read particles. Will be size 0 if no particles.
         Info<< "    Reading particle positions" << endl;
         passiveParticleCloud myCloud(mesh, cloudName);
 
+        pointField localPositions(myCloud.size(), Zero);
+        scalarField localTimes(myCloud.size(), Zero);
+
+        List<labelField> allOrigIds(Pstream::nProcs());
+        List<labelField> allOrigProcs(Pstream::nProcs());
+
         // Collect the track data on all processors that have positions
-        allPositions[Pstream::myProcNo()].setSize
-        (
-            myCloud.size(),
-            point::zero
-        );
         allOrigIds[Pstream::myProcNo()].setSize(myCloud.size(), Zero);
         allOrigProcs[Pstream::myProcNo()].setSize(myCloud.size(), Zero);
 
         label i = 0;
         for (const passiveParticle& p : myCloud)
         {
-            allPositions[Pstream::myProcNo()][i] = p.position();
             allOrigIds[Pstream::myProcNo()][i] = p.origId();
             allOrigProcs[Pstream::myProcNo()][i] = p.origProc();
+            localPositions[i] = p.position();
+            localTimes[i] = runTime.value();
             ++i;
         }
 
         // Collect the track data on the master processor
-        Pstream::gatherList(allPositions);
         Pstream::gatherList(allOrigIds);
         Pstream::gatherList(allOrigProcs);
 
-        Info<< "    Constructing tracks" << nl << endl;
-        if (Pstream::master())
-        {
-            forAll(allPositions, proci)
-            {
-                forAll(allPositions[proci], i)
-                {
-                    label globalId =
-                        startIds[allOrigProcs[proci][i]]
-                      + allOrigIds[proci][i];
-
-                    if (globalId % sampleFrequency == 0)
-                    {
-                        label trackId = globalId/sampleFrequency;
-                        if (allTracks[trackId].size() < maxPositions)
-                        {
-                            allTracks[trackId].append
-                            (
-                                allPositions[proci][i]
-                            );
-                        }
-                    }
-                }
-            }
-        }
+        objectRegistry obr
+        (
+            IOobject
+            (
+                "cloudFields",
+                runTime.timeName(),
+                runTime
+            )
+        );
+
+        myCloud.readFromFiles(obr, fieldNames);
+
+        // Create track positions and track time fields
+        // (not registered as IOFields)
+        // Note: createTrack is a local #define to reduce arg count...
+        createTrack(localPositions, allTracks);
+        createTrack(localTimes, allTrackTimes);
+
+        // Create the track fields
+        // Note: setFields is a local #define to reduce arg count...
+        //setFields(labelFields, labelFieldNames);
+        setFields(scalarFields, scalarFieldNames);
+        setFields(vectorFields, vectorFieldNames);
+        setFields(sphTensorFields, sphTensorFieldNames);
+        setFields(symTensorFields, symTensorFieldNames);
+        setFields(tensorFields, tensorFieldNames);
     }
 
 
     if (Pstream::master())
     {
         PtrList<coordSet> tracks(allTracks.size());
-        forAll(allTracks, trackI)
+        List<scalarField> times(tracks.size());
+        forAll(tracks, tracki)
         {
             tracks.set
             (
-                trackI,
-                new coordSet
-                (
-                    "track" + Foam::name(trackI),
-                    "distance"
-                )
+                tracki,
+                new coordSet("track" + Foam::name(tracki), "distance")
             );
-            tracks[trackI].transfer(allTracks[trackI]);
+            tracks[tracki].transfer(allTracks[tracki]);
+            times[tracki].transfer(allTrackTimes[tracki]);
         }
 
-        autoPtr<writer<scalar>> scalarFormatterPtr = writer<scalar>::New
-        (
-            setFormat
-        );
+        Info<< nl;
 
-        //OFstream vtkTracks(vtkPath/"particleTracks.vtk");
-        fileName vtkFile
-        (
-            scalarFormatterPtr().getFileName
-            (
-                tracks[0],
-                wordList(0)
-            )
-        );
-
-        OFstream vtkTracks
-        (
-            vtkPath/("particleTracks." + vtkFile.ext())
-        );
+        const label Uid = vectorFieldNames.find(UName);
+        List<vectorField> dirs(nTracks);
 
-        Info<< "\nWriting particle tracks in " << setFormat
-            << " format to " << vtkTracks.name()
-            << nl << endl;
+        if (Uid != -1)
+        {
+            const auto& UTracks = vectorFields[Uid];
+            forAll(UTracks, tracki)
+            {
+                const auto& U = UTracks[tracki];
+                dirs[tracki] = U/(mag(U) + ROOTVSMALL);
+            }
+        }
 
-        scalarFormatterPtr().write
-        (
-            true,
-            tracks,
-            wordList(0),
-            List<List<scalarField>>(0),
-            vtkTracks
-        );
+        // Write track fields
+        // Note: writeFields is a local #define to reduce arg count...
+        //writeFields(allLabelFields, labelFieldNames, tracks);
+        writeFields(scalarFields, scalarFieldNames, tracks, times, dirs);
+        writeFields(vectorFields, vectorFieldNames, tracks, times, dirs);
+        writeFields(sphTensorFields, sphTensorFieldNames, tracks, times, dirs);
+        writeFields(symTensorFields, symTensorFieldNames, tracks, times, dirs);
+        writeFields(tensorFields, tensorFieldNames, tracks, times, dirs);
     }
 
-    Info<< "End\n" << endl;
+    Info<< nl << "End\n" << endl;
 
     return 0;
 }
diff --git a/applications/utilities/postProcessing/lagrangian/steadyParticleTracks/steadyParticleTracks.C b/applications/utilities/postProcessing/lagrangian/steadyParticleTracks/steadyParticleTracks.C
index bc26c09774d02eeb2982679f815a6705378bc6d9..8da9c0715144e7ac52a467011083f9c686a9479a 100644
--- a/applications/utilities/postProcessing/lagrangian/steadyParticleTracks/steadyParticleTracks.C
+++ b/applications/utilities/postProcessing/lagrangian/steadyParticleTracks/steadyParticleTracks.C
@@ -27,7 +27,7 @@ Application
     steadyParticleTracks
 
 Group
-    grpPostProcessingUtilitie
+    grpPostProcessingUtilities
 
 Description
     Generate a legacy VTK file of particle tracks for cases that were
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index f7fd57b8538c9aec0c911c605476ddcf1b4f46fe..9e4e434eb7cf0fbee06c5f0b6e4ca3534fb0626c 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -29,6 +29,14 @@ abaqus/ABAQUSCore.C
 nastran/NASCore.C
 obj/OBJstream.C
 fire/FIRECore.C
+
+gltf/foamGltfAccessor.C
+gltf/foamGltfAnimation.C
+gltf/foamGltfBufferView.C
+gltf/foamGltfMesh.C
+gltf/foamGltfObject.C
+gltf/foamGltfScene.C
+
 starcd/STARCDCore.C
 stl/STLCore.C
 stl/STLReader.C
@@ -76,5 +84,6 @@ $(setWriters)/vtk/vtkSetWriterRunTime.C
 $(setWriters)/xmgrace/xmgraceSetWriterRunTime.C
 $(setWriters)/csv/csvSetWriterRunTime.C
 $(setWriters)/nastran/nastranSetWriterRunTime.C
+$(setWriters)/gltf/gltfSetWriterRunTime.C
 
 LIB = $(FOAM_LIBBIN)/libfileFormats
diff --git a/src/fileFormats/gltf/foamGltfAccessor.C b/src/fileFormats/gltf/foamGltfAccessor.C
new file mode 100644
index 0000000000000000000000000000000000000000..ac28337a15f3459014808c283a75429422f310fa
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfAccessor.C
@@ -0,0 +1,119 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfAccessor.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::accessor::accessor()
+:
+    base(),
+    bufferViewId_(-1),
+    byteOffset_(0),
+    componentType_(-1),
+    count_(-1),
+    type_(""),
+    max_(""),
+    min_(""),
+    minMax_(false)
+{}
+
+
+Foam::glTF::accessor::accessor(const word& name)
+:
+    base("Accessor:"+name),
+    bufferViewId_(-1),
+    byteOffset_(0),
+    componentType_(-1),
+    count_(-1),
+    type_(""),
+    max_(""),
+    min_(""),
+    minMax_(false)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::label& Foam::glTF::accessor::bufferViewId() noexcept
+{
+    return bufferViewId_;
+}
+
+
+Foam::label& Foam::glTF::accessor::byteOffset() noexcept
+{
+    return byteOffset_;
+}
+
+
+Foam::label& Foam::glTF::accessor::componentType() noexcept
+{
+    return componentType_;
+}
+
+
+Foam::label& Foam::glTF::accessor::count() noexcept
+{
+    return count_;
+}
+
+
+Foam::string& Foam::glTF::accessor::type() noexcept
+{
+    return type_;
+}
+
+
+void Foam::glTF::accessor::write(Ostream& os) const
+{
+    os  << indent << "\"bufferView\" : " << bufferViewId_ << ',' << nl
+        << indent << "\"byteOffset\" : " << byteOffset_ << ',' << nl
+        << indent << "\"componentType\" : " << componentType_ << ',' << nl
+        << indent << "\"count\" : " << count_ << ',' << nl
+        << indent << "\"type\" : " << type_;
+
+    if (minMax_)
+    {
+        os  << ',' << nl
+            << indent << "\"max\" : " << max_.c_str() << ',' << nl
+            << indent << "\"min\" : " << min_.c_str();
+    }
+
+    base::write(os);
+}
+
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const glTF::accessor& acc)
+{
+    acc.write(os);
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfAccessor.H b/src/fileFormats/gltf/foamGltfAccessor.H
new file mode 100644
index 0000000000000000000000000000000000000000..76244c458606c22b6c44268d014c4094a3599df0
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfAccessor.H
@@ -0,0 +1,167 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::accessor
+
+Description
+    glTF accessor
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfAccessor.C
+    foamGltfAccessorTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_accessor_H
+#define foam_gltf_accessor_H
+
+#include "foamGltfBase.H"
+#include "Field.H"
+#include "label.H"
+#include "string.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+    class accessor;
+}
+
+Ostream& operator<<(Ostream& os, const glTF::accessor& acc);
+
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class glTF::accessor Declaration
+\*---------------------------------------------------------------------------*/
+
+class accessor
+:
+    public base
+{
+protected:
+
+    // Protected Data
+
+        //- Buffer view index
+        label bufferViewId_;
+
+        //- Byte offset
+        label byteOffset_;
+
+        //- Component type
+        label componentType_;
+
+        //- Data size
+        label count_;
+
+        //- Data type
+        string type_;
+
+        //- Max value.  Note: stored as a string for convenience
+        string max_;
+
+        //- Min value.  Note: stored as a string for convenience
+        string min_;
+
+        //- Flag to indicate whether min and max values are available
+        bool minMax_;
+
+
+    // Protected Member Functions
+
+        //- Return the glTF value type for the given OpenFOAM type
+        template<class Type>
+        static string getValueType();
+
+        //- Stringify the value
+        template<class Type>
+        static string toString(const Type& val);
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        accessor();
+
+        //- Construct from name
+        explicit accessor(const word& name);
+
+
+    //- Destructor
+    ~accessor() = default;
+
+
+    // Public Member Functions
+
+        //- Return the buffer view index
+        label& bufferViewId() noexcept;
+
+        //- Return the byte offset
+        label& byteOffset() noexcept;
+
+        //- Return the component type
+        label& componentType() noexcept;
+
+        //- Return the data size
+        label& count() noexcept;
+
+        //- Return the type
+        string& type() noexcept;
+
+        //- Set the accessor
+        template<class Type>
+        void set(const Field<Type>& fld, bool calcMinMax = true);
+
+        //- Write
+        void write(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "foamGltfAccessorTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfAccessorTemplates.C b/src/fileFormats/gltf/foamGltfAccessorTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..9070964a579f44145e14f0422b98c200467bf554
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfAccessorTemplates.C
@@ -0,0 +1,107 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfAccessor.H"
+#include "exprTraits.H"
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+template<class Type>
+Foam::string Foam::glTF::accessor::getValueType()
+{
+    switch (exprTypeTraits<Type>::value)
+    {
+        case exprTypeTraits<label>::value :
+        case exprTypeTraits<scalar>::value :
+        {
+            return "SCALAR";
+        }
+
+        case exprTypeTraits<vector>::value :
+        {
+            return "VEC3";
+        }
+
+        case exprTypeTraits<tensor>::value :
+        {
+            return "MAT3";
+        }
+
+        default:
+        {
+            FatalErrorInFunction
+                << "Unable to process "
+                << pTraits<Type>::typeName << " fields"
+                << abort(FatalError);
+        }
+    }
+
+    return string();
+}
+
+
+template<class Type>
+Foam::string Foam::glTF::accessor::toString(const Type& val)
+{
+    OStringStream buf;
+    buf << "[ ";
+    for (direction dir = 0; dir < pTraits<Type>::nComponents; ++dir)
+    {
+        if (dir) buf << ", ";
+        buf << float(component(val, dir));
+    }
+    buf << " ]";
+
+    return buf.str();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+void Foam::glTF::accessor::set(const Field<Type>& fld, bool calcMinMax)
+{
+    count_ = fld.size();
+
+    type_ = accessor::getValueType<Type>();
+
+    componentType_ = key(componentTypes::FLOAT);
+
+    minMax_ = calcMinMax;
+
+    if (minMax_)
+    {
+        Type minValue = min(fld);
+        Type maxValue = max(fld);
+
+        min_ = accessor::toString(minValue);
+        max_ = accessor::toString(maxValue);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfAnimation.C b/src/fileFormats/gltf/foamGltfAnimation.C
new file mode 100644
index 0000000000000000000000000000000000000000..e5cd97a1e3bf3b8a3f09ebd92766bddfb7f579bf
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfAnimation.C
@@ -0,0 +1,121 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfAnimation.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::animation::animation()
+:
+    base(),
+    samplers_(),
+    channels_()
+{}
+
+
+Foam::glTF::animation::animation(const word& name)
+:
+    base(name),
+    samplers_(),
+    channels_()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::glTF::animation::addTranslation
+(
+    const label inputId,
+    const label outputId,
+    const label nodeId,
+    const string& interpolation
+)
+{
+    glTFSampler sampler;
+    sampler.input = inputId;
+    sampler.output = outputId;
+    sampler.interpolation = interpolation;
+    samplers_.append(sampler);
+
+    glTFChannel channel;
+    channel.samplerId = samplers_.size() - 1;
+    channel.target.node = nodeId;
+    channel.target.path = "translation";
+    channels_.append(channel);
+}
+
+
+void Foam::glTF::animation::write(Ostream& os) const
+{
+    os  << indent << "\"samplers\" : [" << nl << incrIndent;
+
+    forAll(samplers_, i)
+    {
+        const auto& sampler = samplers_[i];
+
+        os  << indent << "{" << nl << incrIndent
+            << indent << "\"input\" : " << sampler.input << "," << nl
+            << indent << "\"interpolation\" : " << sampler.interpolation
+            << "," << nl
+            << indent << "\"output\" : " << sampler.output << nl
+            << decrIndent << indent << "}";
+
+        if (i != samplers_.size() - 1) os  << "," << nl;
+    }
+
+    os  << nl << decrIndent << indent << "]," << nl;
+
+    os  << indent << "\"channels\" : [" << nl << incrIndent;
+
+    forAll(channels_, i)
+    {
+        const auto& channel = channels_[i];
+
+        os  << indent << "{" << nl << incrIndent
+            << indent << "\"sampler\" : " << channel.samplerId << "," << nl
+            << indent << "\"target\" : {" << incrIndent << nl
+            << indent << "\"node\" : " << channel.target.node << "," << nl
+            << indent << "\"path\" : " << channel.target.path << nl
+            << decrIndent << indent << "}" << nl
+            << decrIndent << indent << "}";
+
+        if (i != channels_.size() - 1) os  << "," << nl;
+    }
+
+    os  << nl << decrIndent << indent << "]";
+}
+
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const glTF::animation& animation)
+{
+    animation.write(os);
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfAnimation.H b/src/fileFormats/gltf/foamGltfAnimation.H
new file mode 100644
index 0000000000000000000000000000000000000000..71a118a41ac6a421bc1b71f9c8eefcdd0eff7d2c
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfAnimation.H
@@ -0,0 +1,147 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::animation
+
+Description
+    glTF animation
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfAnimation.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_animation_H
+#define foam_gltf_animation_H
+
+#include "foamGltfBase.H"
+#include "DynamicList.H"
+#include "Tuple2.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+    class animation;
+}
+
+Ostream& operator<<(Ostream& os, const glTF::animation& animation);
+
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class glTF::animation Declaration
+\*---------------------------------------------------------------------------*/
+
+
+class animation
+:
+    public base
+{
+protected:
+
+    // Local Helpers
+
+        // Sampler
+        struct glTFSampler
+        {
+            label input;
+            string interpolation;
+            label output;
+        };
+
+        // Channel target
+        struct glTFTarget
+        {
+            label node;
+            string path;
+        };
+
+        // Channel
+        struct glTFChannel
+        {
+            label samplerId;
+            glTFTarget target;
+        };
+
+
+
+    // Protected Member Data
+
+        //- Samplers
+        DynamicList<glTFSampler> samplers_;
+
+        //- Channels
+        DynamicList<glTFChannel> channels_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        animation();
+
+        //- Construct with name
+        explicit animation(const word& name);
+
+
+    //- Destructor
+    ~animation() = default;
+
+
+    // Public Member Functions
+
+        //- Add translation
+        void addTranslation
+        (
+            const label inputId,
+            const label outputId,
+            const label nodeId,
+            const string& interpolation
+        );
+
+        //- Write
+        void write(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfBase.H b/src/fileFormats/gltf/foamGltfBase.H
new file mode 100644
index 0000000000000000000000000000000000000000..ba00201007d4ba365521ece7a0bc7c41ea71478a
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfBase.H
@@ -0,0 +1,198 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Namespace
+    Foam::glTF
+
+Description
+    Namespace for handling glTF creation.
+    https://www.khronos.org/registry/glTF/
+
+Class
+    Foam::glTF::base
+
+Description
+    Base class for glTF entities
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_base_H
+#define foam_gltf_base_H
+
+#include "word.H"
+#include "label.H"
+#include "Ostream.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+
+    enum class componentTypes : int
+    {
+        BYTE            = 5120, //!< 1 byte
+        UNSIGNED_BYTE   = 5121, //!< 1 byte
+        SHORT           = 5122, //!< 2 bytes
+        UNSIGNED_SHORT  = 5123, //!< 2 bytes
+        UNSIGNED_INT    = 5125, //!< 4 bytes
+        FLOAT           = 5126  //!< 4 bytes
+    };
+
+    // accessor
+    enum class dataTypes
+    {
+        SCALAR, //!< 1 component
+        VEC2,   //!< 2 components
+        VEC3,   //!< 3 components
+        VEC4,   //!< 4 components
+        MAT2,   //!< 4 components
+        MAT3,   //!< 9 components
+        MAT4    //!< 16 components
+    };
+
+    enum class attributeTypes
+    {
+        POSITION,   //!< VEC3 XYZ vertex positions; requires 'min' and 'max'
+        NORMAL,     //!< VEC3 Normalised XYZ vertex normals
+        TANGENT,    //!< VEC4 XYZW vertex tangents
+        TEXCOORD_0, //!< VEC2 UV texture coordinates
+        TEXCOORD_1, //!< VEC2 UV texture coordinates
+        COLOR_0,    //!< VEC3 (rgb), VEC4 (rgba)
+        JOINTS_0,   //!< VEC4
+        WEIGHTS_0   //!< VEC4
+    };
+
+    // bufferView
+    enum class targetTypes : int
+    {
+        ARRAY_BUFFER = 34962, //!< vertex attributes
+        ELEMENT_ARRAY_BUFFER = 34963 //!< vertex indices
+    };
+
+    enum class primitiveModes : int
+    {
+        POINTS = 0,
+        LINES = 1,
+        LINE_LOOP = 2,
+        LINE_STRIP = 3,
+        TRIANGLES = 4,
+        TRIANGLE_STRIP = 5,
+        TRIANGLE_FAN = 6
+    };
+
+    // Helper function to return the enum value
+    template<class Type>
+    auto key(const Type& t) -> typename std::enable_if
+    <
+        std::is_enum<Type>::value,
+        typename std::underlying_type<Type>::type
+    >::type
+    {
+        return static_cast<typename std::underlying_type<Type>::type>(t);
+    }
+
+/*---------------------------------------------------------------------------*\
+                          Class base Declaration
+\*---------------------------------------------------------------------------*/
+
+class base
+{
+protected:
+
+    // Protected Data
+
+        //- Name
+        word name_;
+
+        //- ID
+        label id_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        base()
+        :
+            id_(-1)
+        {}
+
+        //- Construct with name
+        explicit base(const word& name)
+        :
+            name_(name),
+            id_(-1)
+        {}
+
+
+    //- Destructor
+    ~base() = default;
+
+
+    // Member Functions
+
+        //- Return access to the ID
+        label& id() noexcept
+        {
+            return id_;
+        }
+
+        //- Return const access to the name
+        const word& name() const noexcept
+        {
+            return name_;
+        }
+
+        //- Write
+        void write(Ostream& os) const
+        {
+            os  << "," << nl
+                << indent << "\"name\" : \"" << name_ << "\"";
+        }
+
+
+    // Member Operators
+
+        void operator=(const base& gltf)
+        {
+            name_ = gltf.name_;
+            id_ = gltf.id_;
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfBufferView.C b/src/fileFormats/gltf/foamGltfBufferView.C
new file mode 100644
index 0000000000000000000000000000000000000000..30b423c6a420e3d698cb3eac763a51412e164529
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfBufferView.C
@@ -0,0 +1,113 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfBufferView.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::bufferView::bufferView()
+:
+    base(),
+    buffer_(0),
+    byteOffset_(-1),
+    byteLength_(-1),
+    target_(-1)
+{}
+
+
+Foam::glTF::bufferView::bufferView(const word& name)
+:
+    base("BufferView:"+name),
+    buffer_(0),
+    byteOffset_(-1),
+    byteLength_(-1),
+    target_(-1)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::label& Foam::glTF::bufferView::buffer() noexcept
+{
+    return buffer_;
+}
+
+
+Foam::label& Foam::glTF::bufferView::byteOffset() noexcept
+{
+    return byteOffset_;
+}
+
+
+Foam::label& Foam::glTF::bufferView::byteLength() noexcept
+{
+    return byteLength_;
+}
+
+
+Foam::label& Foam::glTF::bufferView::target() noexcept
+{
+    return target_;
+}
+
+
+void Foam::glTF::bufferView::write(Ostream& os) const
+{
+    os  << indent << "\"buffer\" : " << buffer_ << "," << nl
+        << indent << "\"byteOffset\" : " << byteOffset_ << "," << nl
+        << indent << "\"byteLength\" : " << byteLength_;
+
+    if (target_ != -1)
+    {
+        os  << "," << nl
+            << indent << "\"target\" : " << target_;
+    }
+
+    base::write(os);
+}
+
+
+void Foam::glTF::bufferView::operator=(const glTF::bufferView& bv)
+{
+    base::operator=(bv);
+
+    buffer_ = bv.buffer_;
+    byteOffset_ = bv.byteOffset_;
+    byteLength_ = bv.byteLength_;
+    target_ = bv.target_;
+}
+
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const glTF::bufferView& bv)
+{
+    bv.write(os);
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfBufferView.H b/src/fileFormats/gltf/foamGltfBufferView.H
new file mode 100644
index 0000000000000000000000000000000000000000..080a6c0f72230c314320a0f01b5f145035d72e2e
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfBufferView.H
@@ -0,0 +1,133 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::bufferView
+
+Description
+    glTF buffer view - provides a view/slice of the glTF buffer
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfBufferView.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_bufferview_H
+#define foam_gltf_bufferview_H
+
+#include "foamGltfBase.H"
+#include "Ostream.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+    class bufferView;
+}
+
+Ostream& operator<<(Ostream&, const glTF::bufferView& bv);
+
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                      Class glTF::bufferView Declaration
+\*---------------------------------------------------------------------------*/
+
+class bufferView
+:
+    public base
+{
+protected:
+
+    // Protected Data
+
+        //- Buffer index
+        label buffer_;
+
+        //- Byte offset
+        label byteOffset_;
+
+        //- Byte length
+        label byteLength_;
+
+        //- Target
+        label target_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        bufferView();
+
+        //- Construct with name
+        explicit bufferView(const word& name);
+
+
+    //- Destructor
+    ~bufferView() = default;
+
+
+    // Public Member Functions
+
+        //- Return the buffer index
+        label& buffer() noexcept;
+
+        //- Return the byte offset
+        label& byteOffset() noexcept;
+
+        //- Return the byte length
+        label& byteLength() noexcept;
+
+        //- Return the target
+        label& target() noexcept;
+
+        //- Write
+        void write(Ostream& os) const;
+
+
+    // Member Operators
+
+        void operator=(const bufferView& bv);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfList.C b/src/fileFormats/gltf/foamGltfList.C
new file mode 100644
index 0000000000000000000000000000000000000000..c38f6aec69eaa8a4378cd45a0ab1989a51409fe1
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfList.C
@@ -0,0 +1,122 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfList.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+Type& Foam::glTF::List<Type>::create(const word& name)
+{
+    Type obj(name);
+    obj.id() = data_.size();
+    data_.append(obj);
+
+    return data_.last();
+}
+
+
+template<class Type>
+const Foam::DynamicList<Type>& Foam::glTF::List<Type>::data() const noexcept
+{
+    return data_;
+}
+
+
+template<class Type>
+bool Foam::glTF::List<Type>::empty() const noexcept
+{
+    return data_.empty();
+}
+
+
+template<class Type>
+Foam::label Foam::glTF::List<Type>::size() const noexcept
+{
+    return data_.size();
+}
+
+
+template<class Type>
+void Foam::glTF::List<Type>::write
+(
+    Ostream& os,
+    const word& keyword,
+    bool firstEntry
+)
+{
+    if (empty())
+    {
+        return;
+    }
+
+    if (!firstEntry)
+    {
+        os  << "," << nl;
+    }
+
+    os  << indent << "\"" << keyword << "\" : [" << nl << incrIndent;
+
+    write(os);
+
+    os  << decrIndent << nl << indent << "]";
+}
+
+
+template<class Type>
+void Foam::glTF::List<Type>::write(Ostream& os) const
+{
+    forAll(data_, i)
+    {
+        os  << indent << "{"
+            << nl << incrIndent
+            << data_[i]
+            << nl << decrIndent
+            << indent << "}";
+
+        if (i != data_.size() - 1) os  << "," << nl;
+    }
+}
+
+
+template<class Type>
+Type& Foam::glTF::List<Type>::operator[](const label i)
+{
+    return data_[i];
+}
+
+
+template<class Type>
+Foam::Ostream& Foam::operator<<(Ostream& os, const glTF::List<Type>& lst)
+{
+    lst.write(os);
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfList.H b/src/fileFormats/gltf/foamGltfList.H
new file mode 100644
index 0000000000000000000000000000000000000000..09464a176c172e27ef199c86e0629249807c2bdc
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfList.H
@@ -0,0 +1,125 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::List
+
+Description
+    Container for glTF entities
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfList.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_list_H
+#define foam_gltf_list_H
+
+#include "DynamicList.H"
+#include "Ostream.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+    template<class Type> class List;
+}
+
+template<class Type>
+Ostream& operator<<(Ostream& os, const glTF::List<Type>& lst);
+
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class glTF::List Declaration
+\*---------------------------------------------------------------------------*/
+
+template<class Type>
+class List
+{
+    // Private Data
+
+        //- Storage
+        DynamicList<Type> data_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        List() = default;
+
+
+    //- Destructor
+    ~List() = default;
+
+
+    // Public Member Functions
+
+        //- Helper to create a new Type on the list and set the ID
+        Type& create(const word& name);
+
+        //- Return const access to the underlying list
+        const DynamicList<Type>& data() const noexcept;
+
+        //- List contains no data
+        bool empty() const noexcept;
+
+        //- Return the list size
+        label size() const noexcept;
+
+        //- Write
+        void write(Ostream& os, const word& keyword, bool firstEntry = false);
+
+        //- Write
+        void write(Ostream& os) const;
+
+        Type& operator[](const label i);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "foamGltfList.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfMesh.C b/src/fileFormats/gltf/foamGltfMesh.C
new file mode 100644
index 0000000000000000000000000000000000000000..dd54ad525c3bff10eb73739b098ca02b8c809e36
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfMesh.C
@@ -0,0 +1,109 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfMesh.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::mesh::mesh()
+:
+    base(),
+    fields_(),
+    colours_(),
+    accessorId_(-1)
+{}
+
+
+Foam::glTF::mesh::mesh(const word& name)
+:
+    base(name),
+    fields_(),
+    colours_(),
+    accessorId_(-1)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::label& Foam::glTF::mesh::accessorId() noexcept
+{
+    return accessorId_;
+}
+
+
+void Foam::glTF::mesh::addField(const word& name, const label accessorId)
+{
+    fields_.append(Tuple2<string, label>("_field:" + name, accessorId));
+}
+
+
+void Foam::glTF::mesh::addColour(const label accessorId)
+{
+    colours_.append
+    (
+        Tuple2<string, label>
+        (
+            "COLOR_" + Foam::name(colours_.size()),
+            accessorId
+        )
+    );
+}
+
+
+void Foam::glTF::mesh::write(Ostream& os) const
+{
+    os  << indent << "\"primitives\" : [{" << nl << incrIndent
+        << indent << "\"attributes\" : {" << nl  << incrIndent
+        << indent << "\"POSITION\" : " << accessorId_;
+
+    for (const auto& f : fields_)
+    {
+        os  << "," << nl << indent << f.first() << " : " << f.second();
+    }
+
+    for (const auto& c : colours_)
+    {
+        os  << "," << nl << indent << c.first() <<  " : " << c.second();
+    }
+
+    os  << nl << decrIndent << indent << "}," << nl
+        << indent << "\"mode\" : " << 0 << nl << decrIndent// 0 = POINTS
+        << indent << "}]";
+
+    base::write(os);
+}
+
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const glTF::mesh& mesh)
+{
+    mesh.write(os);
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfMesh.H b/src/fileFormats/gltf/foamGltfMesh.H
new file mode 100644
index 0000000000000000000000000000000000000000..c4ef24a362c492cf81f240f61396fa1d336af8a8
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfMesh.H
@@ -0,0 +1,121 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::mesh
+
+Description
+    glTF mesh
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfMesh.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_mesh_H
+#define foam_gltf_mesh_H
+
+#include "foamGltfBase.H"
+#include "DynamicList.H"
+#include "Tuple2.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+    class mesh;
+}
+
+Ostream& operator<<(Ostream& os, const glTF::mesh& mesh);
+
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class glTF::mesh Declaration
+\*---------------------------------------------------------------------------*/
+
+class mesh
+:
+    public base
+{
+    // Private Data
+
+        //- List of fields (name, accessor ID)
+        DynamicList<Tuple2<string, label>> fields_;
+
+        //- List of colours (name, accessor ID)
+        DynamicList<Tuple2<string, label>> colours_;
+
+        //- Accessor ID
+        label accessorId_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        mesh();
+
+        //- Construct from name
+        explicit mesh(const word& name);
+
+
+    //- Destructor
+    ~mesh() = default;
+
+
+    // Public Member Functions
+
+        //- Return the accessor ID
+        label& accessorId() noexcept;
+
+        //- Add a field to the mesh
+        void addField(const word& name, const label accessorId);
+
+        //- Add a colour to the mesh
+        void addColour(const label accessorId);
+
+        //- Write
+        void write(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfObject.C b/src/fileFormats/gltf/foamGltfObject.C
new file mode 100644
index 0000000000000000000000000000000000000000..4290e606ce7537479171e45f451be5e68e157cda
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfObject.C
@@ -0,0 +1,69 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfObject.H"
+#include "endian.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::object::object()
+:
+    base(),
+    data_()
+{
+    #ifdef WM_LITTLE_ENDIAN
+    #else
+    FatalErrorInFunction
+        << "Big-endian buffer support is not available"
+        << abort(FatalError);
+    #endif
+}
+
+
+Foam::glTF::object::object(const word& name)
+:
+    base(name),
+    data_()
+{
+    #ifdef WM_LITTLE_ENDIAN
+    #else
+    FatalErrorInFunction
+        << "Big-endian buffer support is not available"
+        << abort(FatalError);
+    #endif
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::List<float>& Foam::glTF::object::data() const noexcept
+{
+    return data_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfObject.H b/src/fileFormats/gltf/foamGltfObject.H
new file mode 100644
index 0000000000000000000000000000000000000000..614d00451ad6d568bcf4e0c9dd5e471f5cf82373
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfObject.H
@@ -0,0 +1,110 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::object
+
+Description
+    glTF binary object
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfObject.C
+    foamGltfObjectTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_object_H
+#define foam_gltf_object_H
+
+#include "foamGltfBase.H"
+#include "List.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class glTF::object Declaration
+\*---------------------------------------------------------------------------*/
+
+class object
+:
+    public base
+{
+    // Private Data
+
+        //- Buffer storage
+        Foam::List<float> data_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        object();
+
+        //- Construct with name
+        explicit object(const word& name);
+
+
+    // Public Member Functions
+
+        //- Add data to the buffer
+        template<class Type>
+        void addData(const Type& fld);
+
+        //- Add data to the buffer from 2 containers of the same size
+        //  E.g. to combine vector and scalar to create RGBA data
+        template<class Type1, class Type2>
+        void addData(const Type1& fld1, const Type2& fld2);
+
+        //- Return const access to the data buffer
+        const Foam::List<float>& data() const noexcept;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "foamGltfObjectTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfObjectTemplates.C b/src/fileFormats/gltf/foamGltfObjectTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..df8247e5ca1cc18446937d55260cf7c88dbd296c
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfObjectTemplates.C
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+template<class Type>
+void Foam::glTF::object::addData(const Type& fld)
+{
+    const label nComponents =
+        pTraits<typename Type::value_type>::nComponents;
+
+    label count = data_.size();
+    data_.setSize(data_.size() + fld.size()*nComponents);
+
+    forAll(fld, fieldi)
+    {
+        for (direction d = 0; d < nComponents; ++d)
+        {
+            data_[count++] = component(fld[fieldi], d);
+        }
+    }
+}
+
+
+template<class Type1, class Type2>
+void Foam::glTF::object::addData(const Type1& fld1, const Type2&fld2)
+{
+    if (fld1.size() != fld2.size())
+    {
+        FatalErrorInFunction
+            << "Field lengths must be the same. Field1:"
+            << fld1.size() << " Field2:" << fld2.size()
+            << abort(FatalError);
+    }
+
+    const label nComponents1 =
+        pTraits<typename Type1::value_type>::nComponents;
+
+    const label nComponents2 =
+        pTraits<typename Type2::value_type>::nComponents;
+
+    label count = data_.size();
+    data_.setSize
+    (
+        data_.size() + fld1.size()*(nComponents1 + nComponents2)
+    );
+
+    forAll(fld1, fieldi)
+    {
+        for (direction d = 0; d < nComponents1; ++d)
+        {
+            data_[count++] = component(fld1[fieldi], d);
+        }
+
+        for (direction d = 0; d < nComponents2; ++d)
+        {
+            data_[count++] = component(fld2[fieldi], d);
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfScene.C b/src/fileFormats/gltf/foamGltfScene.C
new file mode 100644
index 0000000000000000000000000000000000000000..ba003ce3a38ecfcd04f82cdf0b7aad108f15c0d6
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfScene.C
@@ -0,0 +1,241 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamGltfScene.H"
+#include "fileName.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::glTF::scene::scene()
+:
+    objects_(),
+    meshes_(),
+    bufferViews_(),
+    accessors_(),
+    animations_(),
+    bytes_(0)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::label Foam::glTF::scene::addColourToMesh
+(
+    const vectorField& fld,
+    const word& name,
+    const label meshi,
+    const scalarField& alpha
+)
+{
+    if (meshi > meshes_.size() - 1)
+    {
+        FatalErrorInFunction
+            << "Mesh " << meshi << " out of range "
+            << (meshes_.size() - 1)
+            << abort(FatalError);
+    }
+
+    auto& bv = bufferViews_.create(name);
+    bv.byteOffset() = bytes_;
+    bv.byteLength() = fld.size()*3*sizeof(float); // 3 components
+    bv.target() = key(targetTypes::ARRAY_BUFFER);
+    bytes_ += bv.byteLength();
+
+    auto& acc = accessors_.create(name);
+    acc.bufferViewId() = bv.id();
+    acc.set(fld, false); // no min-max
+
+    auto& obj = objects_.create(name);
+
+    if (alpha.size())
+    {
+        bv.byteLength() += fld.size()*sizeof(float);
+        bytes_ += fld.size()*sizeof(float);
+
+        acc.type() = "VEC4";
+
+        obj.addData(fld, alpha);
+    }
+    else
+    {
+        obj.addData(fld);
+    }
+
+    meshes_[meshi].addColour(acc.id());
+
+    return acc.id();
+}
+
+
+Foam::label Foam::glTF::scene::createAnimation(const word& name)
+{
+    animations_.create(name);
+    return animations_.size() - 1;
+}
+
+
+void Foam::glTF::scene::addToAnimation
+(
+    const label animationi,
+    const label inputId,
+    const label outputId,
+    const label meshId,
+    const string& interpolation
+)
+{
+    if (animationi > animations_.size() - 1)
+    {
+        FatalErrorInFunction
+            << "Animation " << animationi << " out of range "
+            << (animations_.size() - 1)
+            << abort(FatalError);
+    }
+
+    const label nodeId = meshId + 1; // offset by 1 for parent node
+
+    // Note
+    // using 1 mesh per node +1 parent node => meshes_.size() nodes in total
+    if (nodeId > meshes_.size())
+    {
+        FatalErrorInFunction
+            << "Node " << nodeId << " out of range " << meshes_.size()
+            << abort(FatalError);
+    }
+
+    animations_[animationi].addTranslation
+    (
+        inputId,
+        outputId,
+        nodeId,
+        interpolation
+    );
+}
+
+
+void Foam::glTF::scene::write(Ostream& os)
+{
+    const fileName base(os.name().lessExt());
+    const fileName binFile
+    (
+        fileName::concat(base.path(), fileName::name(base) + ".bin")
+    );
+
+    // Write binary file
+    // Note: using stdStream
+    OFstream bin(binFile, IOstream::BINARY);
+    auto& osbin = bin.stdStream();
+
+    label totalBytes = 0;
+    for (const auto& object : objects_.data())
+    {
+        for (const auto& data : object.data())
+        {
+            osbin.write
+            (
+                reinterpret_cast<const char*>(&data),
+                sizeof(float)
+            );
+
+            totalBytes += sizeof(float);
+        }
+    }
+
+    // Write json file
+    os << "{" << nl << incrIndent;
+
+    os  << indent << "\"asset\" : {" << nl << incrIndent
+        << indent << "\"generator\" : \"OpenFOAM - www.openfoam.com\"," << nl
+        << indent << "\"version\" : \"2.0\"" << nl << decrIndent
+        << indent << "}," << nl;
+
+    os  << indent << "\"extras\" : {" << nl << incrIndent
+        /* << content */
+        << decrIndent
+        << indent << "}," << nl;
+
+    os  << indent << "\"scene\": 0," << nl;
+
+    os  << indent << "\"scenes\": [{" << nl << incrIndent
+        << indent << "\"nodes\" : [0]" << nl << decrIndent
+        << indent << "}]," << nl;
+
+    os  << indent << "\"buffers\" : [{" << nl << incrIndent
+        << indent << "\"uri\" : " << string(fileName::name(binFile))
+        << "," << nl
+        << indent << "\"byteLength\" : " << totalBytes << nl << decrIndent
+        << indent << "}]," << nl;
+
+    os  << indent << "\"nodes\" : [" << nl << incrIndent
+        << indent << "{" << nl << incrIndent
+        << indent << "\"children\" : [" << nl << incrIndent;
+
+    // List of child node indices
+    os  << indent;
+    forAll(meshes_, meshi)
+    {
+        const label nodeId = meshi + 1;
+
+        os  <<  nodeId;
+
+        if (meshi != meshes_.size() - 1) os  << ", ";
+
+        if ((meshi+1) % 10 == 0) os << nl << indent;
+    }
+
+    os  << decrIndent << nl << indent << "]," << nl
+        << indent << "\"name\" : \"parent\"" << nl << decrIndent
+        << indent << "}," << nl;
+
+    // List of child meshes
+    forAll(meshes_, meshi)
+    {
+        os  << indent << "{" << nl << incrIndent
+            << indent << "\"mesh\" : " << meshi << nl << decrIndent
+            << indent << "}";
+
+        if (meshi != meshes_.size() - 1) os  << ",";
+
+        os  << nl;
+    }
+
+    os  << decrIndent << indent << "]";
+
+    meshes_.write(os, "meshes");
+
+    bufferViews_.write(os, "bufferViews");
+
+    accessors_.write(os, "accessors");
+
+    animations_.write(os, "animations");
+
+    os  << nl;
+
+    os  << decrIndent << "}" << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfScene.H b/src/fileFormats/gltf/foamGltfScene.H
new file mode 100644
index 0000000000000000000000000000000000000000..27f85f9ad032e524eb685cda84f79e0d7c35d880
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfScene.H
@@ -0,0 +1,162 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::glTF::scene
+
+Description
+    Main class to assemble glTF components into a scene
+
+Note
+    Implements the glTF v2 specification
+
+SourceFiles
+    foamGltfScene.C
+    foamGltfSceneTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foam_gltf_scene_H
+#define foam_gltf_scene_H
+
+#include "foamGltfList.H"
+#include "foamGltfObject.H"
+#include "foamGltfMesh.H"
+#include "foamGltfBufferView.H"
+#include "foamGltfAccessor.H"
+#include "foamGltfAnimation.H"
+#include "scalarField.H"
+#include "vectorField.H"
+#include "OFstream.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace glTF
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class glTF::scene Declaration
+\*---------------------------------------------------------------------------*/
+
+class scene
+{
+    // Private Data
+
+        //- List of binary objects
+        glTF::List<object> objects_;
+
+        //- List of meshes
+        glTF::List<mesh> meshes_;
+
+        //- List of buffer views
+        glTF::List<bufferView> bufferViews_;
+
+        //- List of accessors
+        glTF::List<accessor> accessors_;
+
+        //- List of animations
+        glTF::List<animation> animations_;
+
+        //- Accumulative size in bytes
+        label bytes_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct
+        scene();
+
+
+    // Public Member Functions
+
+        //- Returns accessor index
+        template<class Type>
+        label addField
+        (
+            const Type& fld,
+            const word& name,
+            const label target = -1
+        );
+
+        //- Returns index of last mesh
+        template<class Type>
+        label addMesh(const Type& fld, const word& name);
+
+        //- Returns accessor index
+        template<class Type>
+        label addFieldToMesh
+        (
+            const Type& fld,
+            const word& name,
+            const label meshi
+        );
+
+        //- Returns accessor index
+        label addColourToMesh
+        (
+            const vectorField& fld,
+            const word& name,
+            const label meshi,
+            const scalarField& alpha = scalarField()
+        );
+
+        //- Returns index of last animation
+        label createAnimation(const word& name);
+
+        //- Add to existing animation
+        void addToAnimation
+        (
+            const label animationi,
+            const label inputId,
+            const label outputId,
+            const label meshId,
+            const string& interpolation = "LINEAR"
+        );
+
+        //- Write to stream (JSON and binary data)
+        void write(Ostream& os);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace glTF
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "foamGltfSceneTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/gltf/foamGltfSceneTemplates.C b/src/fileFormats/gltf/foamGltfSceneTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..7ab03f2f8f5d532c60aa6e9542527840f722cb09
--- /dev/null
+++ b/src/fileFormats/gltf/foamGltfSceneTemplates.C
@@ -0,0 +1,95 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+template<class Type>
+Foam::label Foam::glTF::scene::addField
+(
+    const Type& fld,
+    const word& name,
+    const label target
+)
+{
+    const label nComponents = pTraits<typename Type::value_type>::nComponents;
+
+    auto& bv = bufferViews_.create(name);
+    bv.byteOffset() = bytes_;
+    bv.byteLength() = fld.size()*nComponents*sizeof(float);
+    if (target != -1)
+    {
+        bv.target() = target;
+    }
+    bytes_ += bv.byteLength();
+
+    auto& acc = accessors_.create(name);
+    acc.bufferViewId() = bv.id();
+    acc.set(fld);
+
+    auto& obj = objects_.create(name);
+    obj.addData(fld);
+
+    return acc.id();
+}
+
+
+template<class Type>
+Foam::label Foam::glTF::scene::addMesh(const Type& fld, const word& name)
+{
+    const label accessorId =
+        addField(fld, name, key(targetTypes::ARRAY_BUFFER));
+
+    auto& mesh = meshes_.create(name);
+    mesh.accessorId() = accessorId;
+
+    return meshes_.size() - 1;
+}
+
+
+template<class Type>
+Foam::label Foam::glTF::scene::addFieldToMesh
+(
+    const Type& fld,
+    const word& name,
+    const label meshi
+)
+{
+    if (meshi > meshes_.size() - 1)
+    {
+        FatalErrorInFunction
+            << "Mesh " << meshi << " out of range "
+            << (meshes_.size() - 1)
+            << abort(FatalError);
+    }
+
+    const label accessorId = addField(fld, name);
+
+    meshes_[meshi].addField(name, accessorId);
+
+    return accessorId;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/sampledSetWriters/csv/csvSetWriter.C b/src/fileFormats/sampledSetWriters/csv/csvSetWriter.C
index fc469c5e5ebc1bea6e57566de34a161983f21e77..c24ebead3453f07521e1a4e33e1641b480cdf30b 100644
--- a/src/fileFormats/sampledSetWriters/csv/csvSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/csv/csvSetWriter.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -39,10 +40,10 @@ Foam::csvSetWriter<Type>::csvSetWriter()
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
 template<class Type>
-Foam::csvSetWriter<Type>::~csvSetWriter()
+Foam::csvSetWriter<Type>::csvSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -86,13 +87,14 @@ template<class Type>
 void Foam::csvSetWriter<Type>::write
 (
     const bool writeTracks,
-    const PtrList<coordSet>& points,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
     Ostream& os
 ) const
 {
-    writeHeader(points[0],valueSetNames,os);
+    writeHeader(tracks[0],valueSetNames,os);
 
     if (valueSets.size() != valueSetNames.size())
     {
@@ -104,7 +106,7 @@ void Foam::csvSetWriter<Type>::write
 
     List<const List<Type>*> columns(valueSets.size());
 
-    forAll(points, trackI)
+    forAll(tracks, trackI)
     {
         // Collect sets into columns
         forAll(valueSets, i)
@@ -112,7 +114,7 @@ void Foam::csvSetWriter<Type>::write
             columns[i] = &valueSets[i][trackI];
         }
 
-        this->writeTable(points[trackI], columns, os);
+        this->writeTable(tracks[trackI], columns, os);
         os  << nl << nl;
     }
 }
diff --git a/src/fileFormats/sampledSetWriters/csv/csvSetWriter.H b/src/fileFormats/sampledSetWriters/csv/csvSetWriter.H
index 83c724b8069107089775f0c6f41c9c490c625102..81026527ae5b0545debab26beff02b903fc4cc72 100644
--- a/src/fileFormats/sampledSetWriters/csv/csvSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/csv/csvSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -74,12 +75,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         csvSetWriter();
 
+        //- Construct with dictionary
+        explicit csvSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~csvSetWriter();
+    virtual ~csvSetWriter() = default;
 
 
     // Member Functions
@@ -101,9 +105,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.C b/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.C
index 46880d0bd92353c907414af5af9fa92f12bfe535..44c896bb19413c3665f8821d2d5b98be83a91991 100644
--- a/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.C
@@ -41,10 +41,11 @@ Foam::ensightSetWriter<Type>::ensightSetWriter()
     writer<Type>()
 {}
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 template<class Type>
-Foam::ensightSetWriter<Type>::~ensightSetWriter()
+Foam::ensightSetWriter<Type>::ensightSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -174,6 +175,7 @@ template<class Type>
 void Foam::ensightSetWriter<Type>::write
 (
     const bool writeTracks,
+    const List<scalarField>& times,
     const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
diff --git a/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.H b/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.H
index d98a32b2ef54e4574dfb4a0d7da84f9272540f7b..c0ebfd1e7173622199122bf4b3f3fe1e9fc6e8ee 100644
--- a/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/ensight/ensightSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -61,12 +62,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         ensightSetWriter();
 
+        //- Construct with dictionary
+        explicit ensightSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~ensightSetWriter();
+    virtual ~ensightSetWriter() = default;
 
 
     // Member Functions
@@ -88,9 +92,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.C b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.C
new file mode 100644
index 0000000000000000000000000000000000000000..295d1902dd78ab70e2e6186f6d924e2c290eb655
--- /dev/null
+++ b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.C
@@ -0,0 +1,625 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "gltfSetWriter.H"
+#include "coordSet.H"
+#include "fileName.H"
+#include "OFstream.H"
+#include "floatVector.H"
+#include "foamGltfScene.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+template<class Type>
+const Foam::Enum<typename Foam::gltfSetWriter<Type>::fieldOption>
+Foam::gltfSetWriter<Type>::fieldOptionNames_
+({
+    { fieldOption::UNIFORM, "uniform" },
+    { fieldOption::FIELD, "field" },
+});
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+Foam::word Foam::gltfSetWriter<Type>::getColourMap
+(
+    const dictionary& dict
+) const
+{
+    word colourMap = colourTable::predefinedNames.names()[0];
+    dict.readIfPresent("colourMap", colourMap);
+
+    return colourMap;
+}
+
+
+template<class Type>
+const Foam::colourTable& Foam::gltfSetWriter<Type>::getColourTable
+(
+    const dictionary& dict
+) const
+{
+    return colourTable::ref(getColourMap(dict));
+}
+
+
+template<class Type>
+Foam::scalar Foam::gltfSetWriter<Type>::getFieldMin
+(
+    const word& fieldName
+) const
+{
+    const dictionary fieldDict = fieldInfoDict_.subOrEmptyDict(fieldName);
+
+    return fieldDict.getOrDefault("min", -GREAT);
+}
+
+
+template<class Type>
+Foam::scalar Foam::gltfSetWriter<Type>::getFieldMax
+(
+    const word& fieldName
+) const
+{
+    const dictionary fieldDict = fieldInfoDict_.subOrEmptyDict(fieldName);
+
+    return fieldDict.getOrDefault("max", GREAT);
+}
+
+
+template<class Type>
+Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getAlphaField
+(
+    const dictionary& dict,
+    const wordList& valueSetNames,
+    const List<const Field<Type>*>& valueSets
+) const
+{
+    if (dict.found("alpha"))
+    {
+        const auto option = fieldOptionNames_.get("alpha", dict);
+
+        switch (option)
+        {
+            case fieldOption::UNIFORM:
+            {
+                const scalar value = dict.getScalar("alphaValue");
+                return tmp<scalarField>::New(valueSets[0]->size(), value);
+            }
+            case fieldOption::FIELD:
+            {
+                const word alphaFieldName = dict.get<word>("alphaField");
+                const bool normalise = dict.get<bool>("normalise");
+                const label i = valueSetNames.find(alphaFieldName);
+                if (i == -1)
+                {
+                    FatalErrorInFunction
+                        << "Unable to find field " << alphaFieldName
+                        << ". Valid field names are:" << valueSetNames
+                        << exit(FatalError);
+                }
+
+                auto tresult =
+                    tmp<scalarField>::New(valueSets[i]->component(0));
+
+                if (normalise)
+                {
+                    tresult.ref() /= mag(tresult() + ROOTVSMALL);
+                }
+
+                return tresult;
+            }
+        }
+    }
+
+    return tmp<scalarField>::New(valueSets[0]->size(), Zero);
+}
+
+
+template<class Type>
+Foam::tmp<Foam::scalarField> Foam::gltfSetWriter<Type>::getTrackAlphaField
+(
+    const dictionary& dict,
+    const wordList& valueSetNames,
+    const List<List<Field<Type>>>& valueSets,
+    const label tracki
+) const
+{
+    if (dict.found("alpha"))
+    {
+        const auto option = fieldOptionNames_.get("alpha", dict);
+
+        switch (option)
+        {
+            case fieldOption::UNIFORM:
+            {
+                const scalar value = dict.getScalar("alphaValue");
+                return tmp<scalarField>::New
+                (
+                    valueSets[0][tracki].size(), value
+                );
+            }
+            case fieldOption::FIELD:
+            {
+                const word alphaFieldName = dict.get<word>("alphaField");
+                const bool normalise = dict.get<bool>("normalise");
+                const label fieldi = valueSetNames.find(alphaFieldName);
+                if (fieldi == -1)
+                {
+                    FatalErrorInFunction
+                        << "Unable to find field " << alphaFieldName
+                        << ". Valid field names are:" << valueSetNames
+                        << exit(FatalError);
+                }
+
+                // Note: selecting the first component!
+                auto tresult =
+                    tmp<scalarField>::New
+                    (
+                        valueSets[fieldi][tracki].component(0)
+                    );
+
+                if (normalise)
+                {
+                    tresult.ref() /= mag(tresult() + ROOTVSMALL);
+                }
+
+                return tresult;
+            }
+        }
+    }
+
+    return tmp<scalarField>::New(valueSets[0][tracki].size(), Zero);
+}
+
+
+template<class Type>
+Foam::vector Foam::gltfSetWriter<Type>::getTrackAnimationColour
+(
+    const colourTable& colours,
+    const wordList& valueSetNames,
+    const List<List<Field<Type>>>& valueSets,
+    const label tracki
+) const
+{
+    if (!colour_)
+    {
+        FatalErrorInFunction
+            << "Attempting to get colour when colour option is off"
+            << abort(FatalError);
+    }
+
+    const auto option = fieldOptionNames_.get("colour", animationDict_);
+
+    switch (option)
+    {
+        case fieldOption::UNIFORM:
+        {
+            return animationDict_.get<vector>("colourValue");
+        }
+        case fieldOption::FIELD:
+        {
+            const word fieldName = animationDict_.get<word>("colourField");
+            const label fieldi = valueSetNames.find(fieldName);
+            if (fieldi == -1)
+            {
+                FatalErrorInFunction
+                    << "Unable to find field " << fieldName
+                    << ". Valid field names are:" << valueSetNames
+                    << exit(FatalError);
+            }
+
+            // Note: selecting the first component!
+
+            scalar minValue;
+            scalar maxValue;
+            if (!animationDict_.readIfPresent("min", minValue))
+            {
+                minValue = min(valueSets[fieldi][tracki].component(0));
+            }
+            if (!animationDict_.readIfPresent("max", maxValue))
+            {
+                maxValue = max(valueSets[fieldi][tracki].component(0));
+            }
+            const scalar refValue = component(valueSets[fieldi][tracki][0], 0);
+            const scalar fraction =
+                (refValue - minValue)/(maxValue - minValue + ROOTVSMALL);
+
+            return (colours.value(max(0, min(1, fraction))));
+        }
+    }
+
+    return vector::zero;
+}
+
+
+template<class Type>
+Foam::tmp<Foam::vectorField> Foam::gltfSetWriter<Type>::directions
+(
+    const coordSet& points
+) const
+{
+    auto tresult = tmp<vectorField>::New(points.size(), Zero);
+    auto& result = tresult.ref();
+
+    if (points.size() > 1)
+    {
+        for (label i = 1; i < points.size(); ++i)
+        {
+            result[i-1] = points[i] - points[i-1];
+            result[i-1].normalise();
+        }
+
+        result.last() = result[points.size()-2];
+    }
+
+
+    return tresult;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+template<class Type>
+Foam::gltfSetWriter<Type>::gltfSetWriter()
+:
+    writer<Type>(),
+    animate_(false),
+    colour_(false),
+    fieldInfoDict_(),
+    animationDict_()
+{}
+
+
+template<class Type>
+Foam::gltfSetWriter<Type>::gltfSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict),
+    animate_(dict.getOrDefault("animate", false)),
+    colour_(dict.getOrDefault("colour", false)),
+    fieldInfoDict_(dict.subOrEmptyDict("fieldInfo")),
+    animationDict_(dict.subOrEmptyDict("animationInfo"))
+{
+    // fieldInfo
+    // {
+    //     U
+    //     {
+    //         colourMap       coolToWarm; // others...
+    //         min             10;
+    //         max             100;
+    //         alpha           field; // uniform|field
+    //         alphaField      ageOfAir;
+    //     }
+    // }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+Foam::fileName Foam::gltfSetWriter<Type>::getFileName
+(
+    const coordSet& points,
+    const wordList& valueSetNames
+) const
+{
+    return this->getBaseName(points, valueSetNames) + ".gltf";
+}
+
+
+template<class Type>
+void Foam::gltfSetWriter<Type>::write
+(
+    const coordSet& points,
+    const wordList& valueSetNames,
+    const List<const Field<Type>*>& valueSets,
+    Ostream& os
+) const
+{
+    if (valueSets.size() != valueSetNames.size())
+    {
+        FatalErrorInFunction
+            << "Number of variables:" << valueSetNames.size() << endl
+            << "Number of valueSets:" << valueSets.size()
+            << exit(FatalError);
+    }
+
+    glTF::scene scene;
+    const label meshi = scene.addMesh(points, "points");
+    forAll(valueSetNames, i)
+    {
+        scene.addFieldToMesh(*valueSets[i], valueSetNames[i], meshi);
+    }
+
+    if (colour_)
+    {
+        forAll(valueSets, fieldi)
+        {
+            const auto& field = *valueSets[fieldi];
+            const word& fieldName = valueSetNames[fieldi];
+            const dictionary dict = fieldInfoDict_.subOrEmptyDict(fieldName);
+            const auto& colours = getColourTable(dict);
+
+            const auto talpha =
+                 getAlphaField(dict, valueSetNames, valueSets);
+            const scalarField& alpha = talpha();
+
+            const Type maxValue = max(field);
+            const Type minValue = min(field);
+
+            const scalar minValueLimit = getFieldMin(fieldName);
+            const scalar maxValueLimit = getFieldMax(fieldName);
+
+            for (direction cmpti=0; cmpti < pTraits<Type>::nComponents; ++cmpti)
+            {
+                vectorField fieldColour(field.size());
+
+                forAll(field, i)
+                {
+                    const Type& v = field[i];
+                    float f = component(v, cmpti);
+                    float minf = max(component(minValue, cmpti), minValueLimit);
+                    float maxf = min(component(maxValue, cmpti), maxValueLimit);
+                    float deltaf = (maxf - minf + SMALL);
+
+                    fieldColour[i] =
+                        colours.value(min(max((f - minf)/deltaf, 0), 1));
+                }
+
+                scene.addColourToMesh
+                (
+                    fieldColour,
+                    "Colour:" + fieldName + Foam::name(cmpti),
+                    meshi,
+                    alpha
+                );
+            }
+        }
+    }
+
+    scene.write(os);
+}
+
+
+template<class Type>
+void Foam::gltfSetWriter<Type>::write
+(
+    const bool writeTracks,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
+    const wordList& valueSetNames,
+    const List<List<Field<Type>>>& valueSets,
+    Ostream& os
+) const
+{
+    if (valueSets.size() != valueSetNames.size())
+    {
+        FatalErrorInFunction
+            << "Number of variables:" << valueSetNames.size() << endl
+            << "Number of valueSets:" << valueSets.size()
+            << exit(FatalError);
+    }
+
+    if (animate_)
+    {
+        writeAnimateTracks
+        (
+            writeTracks,
+            times,
+            tracks,
+            valueSetNames,
+            valueSets,
+            os
+        );
+    }
+    else
+    {
+        writeStaticTracks
+        (
+            writeTracks,
+            times,
+            tracks,
+            valueSetNames,
+            valueSets,
+            os
+        );
+    }
+}
+
+
+template<class Type>
+void Foam::gltfSetWriter<Type>::writeStaticTracks
+(
+    const bool writeTracks,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
+    const wordList& valueSetNames,
+    const List<List<Field<Type>>>& valueSets,
+    Ostream& os
+) const
+{
+    glTF::scene scene;
+    forAll(tracks, tracki)
+    {
+        const vectorField& track = tracks[tracki];
+        const label meshi = scene.addMesh(track, "track:" + Foam::name(tracki));
+        forAll(valueSetNames, fieldi)
+        {
+            const word& fieldName = valueSetNames[fieldi];
+            const auto& field = valueSets[fieldi][tracki];
+            scene.addFieldToMesh(field, fieldName, meshi);
+        }
+
+        if (colour_)
+        {
+            forAll(valueSets, fieldi)
+            {
+                const auto& field = valueSets[fieldi][tracki];
+                const word& fieldName = valueSetNames[fieldi];
+                const dictionary dict =
+                    fieldInfoDict_.subOrEmptyDict(fieldName);
+                const auto& colours = getColourTable(dict);
+
+                const auto talpha =
+                    getTrackAlphaField(dict, valueSetNames, valueSets, tracki);
+                const scalarField& alpha = talpha();
+
+                const Type maxValue = max(field);
+                const Type minValue = min(field);
+
+                const scalar minValueLimit = getFieldMin(fieldName);
+                const scalar maxValueLimit = getFieldMax(fieldName);
+
+                for
+                (
+                    direction cmpti=0;
+                    cmpti < pTraits<Type>::nComponents;
+                    ++cmpti
+                )
+                {
+                    vectorField fieldColour(field.size(), Zero);
+
+                    forAll(field, i)
+                    {
+                        const Type& v = field[i];
+                        float f = component(v, cmpti);
+                        float minf =
+                            max(component(minValue, cmpti), minValueLimit);
+                        float maxf =
+                            min(component(maxValue, cmpti), maxValueLimit);
+                        float deltaf = (maxf - minf + SMALL);
+
+                        fieldColour[i] =
+                           colours.value(min(max((f - minf)/deltaf, 0), 1));
+                    }
+
+                    scene.addColourToMesh
+                    (
+                        fieldColour,
+                        "Colour:" + fieldName + Foam::name(cmpti),
+                        meshi,
+                        alpha
+                    );
+                }
+            }
+        }
+    }
+
+    scene.write(os);
+}
+
+
+template<class Type>
+void Foam::gltfSetWriter<Type>::writeAnimateTracks
+(
+    const bool writeTracks,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
+    const wordList& valueSetNames,
+    const List<List<Field<Type>>>& valueSets,
+    Ostream& os
+) const
+{
+    const auto& colours = getColourTable(animationDict_);
+
+    glTF::scene scene;
+    const label animationi = scene.createAnimation("animation");
+
+    forAll(tracks, tracki)
+    {
+        const auto& track = tracks[tracki];
+
+        if (track.empty())
+        {
+            continue;
+        }
+
+        // Seed starting positions and field values
+        const label meshi =
+            scene.addMesh
+            (
+                vectorField(1, track[0]),
+                "track:" + Foam::name(tracki)
+            );
+
+        forAll(valueSetNames, fieldi)
+        {
+            const Field<Type>& field = valueSets[fieldi][tracki];
+            const word& fieldName = valueSetNames[fieldi];
+            scene.addFieldToMesh(Field<Type>(1, field[0]), fieldName, meshi);
+        }
+
+        // Time frames
+        const label timeId =
+            scene.addField(times[tracki], "time:" + Foam::name(tracki));
+
+        // Translations
+        const vectorField translation(track - track[0]);
+        const label translationId = scene.addField(translation, "translation");
+
+        scene.addToAnimation(animationi, timeId, translationId, meshi);
+
+        // Note: colours cannot be animated... setting a fixed value
+        if (colour_)
+        {
+            const vector colour =
+                getTrackAnimationColour
+                (
+                    colours,
+                    valueSetNames,
+                    valueSets,
+                    tracki
+                );
+
+            const auto talpha =
+                getTrackAlphaField
+                (
+                    animationDict_,
+                    valueSetNames,
+                    valueSets,
+                    tracki
+                );
+
+            const scalarField& alpha = talpha();
+
+            scene.addColourToMesh
+            (
+                vectorField(1, colour),
+                "Colour:fixed",
+                meshi,
+                scalarField(1, alpha[0])
+            );
+        }
+    }
+
+    scene.write(os);
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.H b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.H
new file mode 100644
index 0000000000000000000000000000000000000000..b4936440f9530cd9e78fb661621c608e80e2596a
--- /dev/null
+++ b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriter.H
@@ -0,0 +1,312 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::gltfSetWriter
+
+Description
+    Writes point data in glTF v2 format
+
+    Two files are generated:
+    - filename.bin  : a binary file containing all scene entities
+    - filename.gltf : a JSON file that ties fields to the binary data
+
+    The output can contain both geometry and fields, with additional support
+    for colours using a user-supplied colour map, and animation of particle
+    tracks.
+
+    Controls are provided via the optional formatOptions dictionary.
+
+    For non-particle track data:
+
+    \verbatim
+    formatOptions
+    {
+        // Apply colours flag (yes | no ) [optional]
+        colours     yes;
+
+        // List of options per field
+        fieldInfo
+        {
+            p
+            {
+                // Colour map [optional]
+                colourMap       <colourMap>;
+
+                // Colour map minimum and maximum limits [optional]
+                // Uses field min and max if not specified
+                min             0;
+                max             1;
+
+                // Alpha channel [optional] (uniform | field)
+                alpha           uniform;
+                alphaValue      0.5;
+
+                //alpha           field;
+                //alphaField      T;
+                //normalise       yes;
+            }
+        }
+    }
+    \verbatim
+
+    For particle tracks:
+
+    \verbatim
+    formatOptions
+    {
+        // Apply colours flag (yes | no) [optional]
+        colours     yes;
+
+        // Animate tracks (yes | no) [optional]
+        animate     yes;
+
+        // Animation properties [optional]
+        animationInfo
+        {
+            // Colour map [optional]
+            colourMap       <colourMap>;
+
+            // Colour [optional] (uniform | field)
+            colour          uniform;
+            colourValue     (1 0 0); // RGB in range [0-1]
+
+            //colour          field;
+            //colourField     d;
+
+            // Colour map minimum and maximum limits [optional]
+            // Note: for colour = field option
+            // Uses field min and max if not specified
+            min             0;
+            max             1;
+
+            // Alpha channel [optional] (uniform | field)
+            alpha           uniform;
+            alphaValue      0.5;
+
+            //alpha           field;
+            //alphaField      T;
+            //normalise       yes;
+        }
+    }
+    \endverbatim
+
+Note
+    When writing particle animations, the particle field and colour properties
+    correspond to initial particle state (first data point) and cannot be
+    animated (limitation of the file format).
+
+    For more information on the specification see
+    https://www.khronos.org/registry/glTF/
+
+SourceFiles
+    gltfSetWriter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef writers_gltfSetWriter_H
+#define writers_gltfSetWriter_H
+
+#include "writer.H"
+#include "colourTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class gltfSetWriter Declaration
+\*---------------------------------------------------------------------------*/
+
+template<class Type>
+class gltfSetWriter
+:
+    public writer<Type>
+{
+public:
+
+    // Enumerations
+
+        //- Field option used for colours
+        enum class fieldOption
+        {
+            UNIFORM,    //!< Uniform value
+            FIELD       //!< field value
+        };
+
+
+    //- Strings corresponding to the field options
+    static const Enum<fieldOption> fieldOptionNames_;
+
+
+private:
+
+    // Private Data
+
+        //- Flag to animate - for particle tracks only
+        bool animate_;
+
+        //- Flag to add field colours
+        bool colour_;
+
+        //- Local field information
+        const dictionary fieldInfoDict_;
+
+        //- Animation information
+        const dictionary animationDict_;
+
+
+    // Private Member Functions
+
+        //- Return the colour map name
+        word getColourMap(const dictionary& dict) const;
+
+        //- Return the colour table corresponding to the colour map
+        const colourTable& getColourTable(const dictionary& dict) const;
+
+        //- Return the field minimum value
+        scalar getFieldMin(const word& fieldName) const;
+
+        //- Return the field maximum value
+        scalar getFieldMax(const word& fieldName) const;
+
+        //- Return the alpha field for mesh values
+        tmp<scalarField> getAlphaField
+        (
+            const dictionary& dict,
+            const wordList& valueSetNames,
+            const List<const Field<Type>*>& valueSets
+        ) const;
+
+        //- Return the alpha field for tracks
+        tmp<scalarField> getTrackAlphaField
+        (
+            const dictionary& dict,
+            const wordList& valueSetNames,
+            const List<List<Field<Type>>>& valueSets,
+            const label tracki
+        ) const;
+
+        //- Return the animation colour when animating tracks
+        vector getTrackAnimationColour
+        (
+            const colourTable& colours,
+            const wordList& valueSetNames,
+            const List<List<Field<Type>>>& valueSets,
+            const label tracki
+        ) const;
+
+        //- Return track orientation/dirrections
+        tmp<vectorField> directions(const coordSet& points) const;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("gltf");
+
+
+    // Constructors
+
+        //- Default construct
+        gltfSetWriter();
+
+        //- Construct from dictionary
+        explicit gltfSetWriter(const dictionary& dict);
+
+
+    //- Destructor
+    virtual ~gltfSetWriter() = default;
+
+
+    // Member Functions
+
+        //- Return the file name
+        virtual fileName getFileName
+        (
+            const coordSet&,
+            const wordList&
+        ) const;
+
+        //- Write
+        virtual void write
+        (
+            const coordSet&,
+            const wordList&,
+            const List<const Field<Type>*>&,
+            Ostream&
+        ) const;
+
+        //- Write tracks (main entry point)
+        virtual void write
+        (
+            const bool writeTracks,
+            const List<scalarField>& times,
+            const PtrList<coordSet>&,
+            const wordList& valueSetNames,
+            const List<List<Field<Type>>>&,
+            Ostream&
+        ) const;
+
+        //- Write animated tracks
+        virtual void writeAnimateTracks
+        (
+            const bool writeTracks,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
+            const wordList& valueSetNames,
+            const List<List<Field<Type>>>& valueSets,
+            Ostream&
+        ) const;
+
+        //- Write static tracks
+        virtual void writeStaticTracks
+        (
+            const bool writeTracks,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
+            const wordList& valueSetNames,
+            const List<List<Field<Type>>>& valueSets,
+            Ostream&
+        ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "gltfSetWriter.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/sampledSetWriters/gltf/gltfSetWriterRunTime.C b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriterRunTime.C
new file mode 100644
index 0000000000000000000000000000000000000000..aeb390b4e9bc48d8b978796507efa1afafe7a2a7
--- /dev/null
+++ b/src/fileFormats/sampledSetWriters/gltf/gltfSetWriterRunTime.C
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "gltfSetWriter.H"
+#include "writers.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    makeSetWriters(gltfSetWriter);
+}
+
+// ************************************************************************* //
diff --git a/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.C b/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.C
index a0e02674909d727a0e6221712753d2a363afdf97..771dddfd1487af0fa6aadb69733e62aefe3648d2 100644
--- a/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,10 +41,11 @@ Foam::gnuplotSetWriter<Type>::gnuplotSetWriter()
     writer<Type>()
 {}
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 template<class Type>
-Foam::gnuplotSetWriter<Type>::~gnuplotSetWriter()
+Foam::gnuplotSetWriter<Type>::gnuplotSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -112,7 +113,8 @@ template<class Type>
 void Foam::gnuplotSetWriter<Type>::write
 (
     const bool writeTracks,
-    const PtrList<coordSet>& trackPoints,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
     Ostream& os
@@ -125,12 +127,12 @@ void Foam::gnuplotSetWriter<Type>::write
             << "Number of valueSets:" << valueSets.size()
             << exit(FatalError);
     }
-    if (trackPoints.size() > 0)
+    if (tracks.size() > 0)
     {
         os  << "set term postscript color" << nl
-            << "set output \"" << trackPoints[0].name() << ".ps\"" << nl;
+            << "set output \"" << tracks[0].name() << ".ps\"" << nl;
 
-        forAll(trackPoints, trackI)
+        forAll(tracks, trackI)
         {
             os  << "plot";
 
@@ -147,7 +149,7 @@ void Foam::gnuplotSetWriter<Type>::write
 
             forAll(valueSets, i)
             {
-                this->writeTable(trackPoints[trackI], valueSets[i][trackI], os);
+                this->writeTable(tracks[trackI], valueSets[i][trackI], os);
                 os  << "e" << nl;
             }
         }
diff --git a/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.H b/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.H
index feb1ace9109d2bbd31d5f550db235f1cc35a7230..da88940954003e554c774b91f68f5b8d794f1c4e 100644
--- a/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/gnuplot/gnuplotSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -52,7 +53,6 @@ class gnuplotSetWriter
 :
     public writer<Type>
 {
-
 public:
 
     //- Runtime type information
@@ -61,12 +61,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         gnuplotSetWriter();
 
+        //- Construct with dictionary
+        explicit gnuplotSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~gnuplotSetWriter();
+    virtual ~gnuplotSetWriter() = default;
 
 
     // Member Functions
@@ -88,9 +91,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.C b/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.C
index 0f6c77fd6ea8a4404c612ee0724cc8a6c344f19e..dcb841af45c3f995660ca0cf10fae8bd6835dd74 100644
--- a/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2012 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +32,6 @@ License
 #include "fileName.H"
 #include "OFstream.H"
 
-
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 template<class Type>
@@ -53,10 +53,10 @@ Foam::jplotSetWriter<Type>::jplotSetWriter()
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
 template<class Type>
-Foam::jplotSetWriter<Type>::~jplotSetWriter()
+Foam::jplotSetWriter<Type>::jplotSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
diff --git a/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.H b/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.H
index e67f5202cec4416b7de431c21432ce8dbc62ca7b..26c0c8f5036141e1e7c458f3e186bd4e5f82c548 100644
--- a/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/jplot/jplotSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -65,12 +66,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         jplotSetWriter();
 
+        //- Construct with dictionary
+        explicit jplotSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~jplotSetWriter();
+    virtual ~jplotSetWriter() = default;
 
 
     // Member Functions
@@ -92,9 +96,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const
         {
diff --git a/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.C b/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.C
index ed2c734ca42e94428158fc4fc72d4df66dc2e99a..5747dda885328d326bac54d0d514fa19a2968edc 100644
--- a/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -38,10 +38,11 @@ Foam::nastranSetWriter<Type>::nastranSetWriter()
     writer<Type>()
 {}
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 template<class Type>
-Foam::nastranSetWriter<Type>::~nastranSetWriter()
+Foam::nastranSetWriter<Type>::nastranSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -129,6 +130,7 @@ template<class Type>
 void Foam::nastranSetWriter<Type>::write
 (
     const bool writeTracks,
+    const List<scalarField>& times,
     const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
diff --git a/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.H b/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.H
index 5ae415744f66c0810237e950e8368d2cbee69aa6..6cdb208c256f927d7e1588316c440dc6b46c594a 100644
--- a/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/nastran/nastranSetWriter.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -56,13 +56,6 @@ class nastranSetWriter
 :
     public writer<Type>
 {
-public:
-
-    //- File field formats
-    using fieldFormat = Foam::fileFormats::NASCore::fieldFormat;
-
-private:
-
     // Private Member Functions
 
         //- Write the formatted keyword to the output stream
@@ -75,18 +68,25 @@ private:
 
 public:
 
+    //- File field formats
+    using fieldFormat = Foam::fileFormats::NASCore::fieldFormat;
+
+
     //- Runtime type information
     TypeName("nastran");
 
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         nastranSetWriter();
 
+        //- Construct with dictionary
+        explicit nastranSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~nastranSetWriter();
+    virtual ~nastranSetWriter() = default;
 
 
     // Member Functions
@@ -108,9 +108,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/raw/rawSetWriter.C b/src/fileFormats/sampledSetWriters/raw/rawSetWriter.C
index 34d99f9968c42f33e79ffcb3b2e394c742e14491..6026e6508aec3b4b57e0b2b2cc2f294bc7ece009 100644
--- a/src/fileFormats/sampledSetWriters/raw/rawSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/raw/rawSetWriter.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -39,10 +40,10 @@ Foam::rawSetWriter<Type>::rawSetWriter()
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
 template<class Type>
-Foam::rawSetWriter<Type>::~rawSetWriter()
+Foam::rawSetWriter<Type>::rawSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -84,7 +85,8 @@ template<class Type>
 void Foam::rawSetWriter<Type>::write
 (
     const bool writeTracks,
-    const PtrList<coordSet>& points,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
     Ostream& os
@@ -100,7 +102,7 @@ void Foam::rawSetWriter<Type>::write
 
     List<const List<Type>*> columns(valueSets.size());
 
-    forAll(points, trackI)
+    forAll(tracks, trackI)
     {
         // Collect sets into columns
         forAll(valueSets, i)
@@ -108,7 +110,7 @@ void Foam::rawSetWriter<Type>::write
             columns[i] = &valueSets[i][trackI];
         }
 
-        this->writeTable(points[trackI], columns, os);
+        this->writeTable(tracks[trackI], columns, os);
         os  << nl << nl;
     }
 }
diff --git a/src/fileFormats/sampledSetWriters/raw/rawSetWriter.H b/src/fileFormats/sampledSetWriters/raw/rawSetWriter.H
index 2109ed78d7fb4b06d58fc483f857cc7110dc1917..87071a9285c9f45e5ec0ca00b6222b1464937304 100644
--- a/src/fileFormats/sampledSetWriters/raw/rawSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/raw/rawSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -52,7 +53,6 @@ class rawSetWriter
 :
     public writer<Type>
 {
-
 public:
 
     //- Runtime type information
@@ -61,12 +61,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         rawSetWriter();
 
+        //- Construct with dictionary
+        explicit rawSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~rawSetWriter();
+    virtual ~rawSetWriter() = default;
 
 
     // Member Functions
@@ -88,9 +91,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.C b/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.C
index 0413bfdb90ec6df0da54a1f909d6f8f63abd1ba2..e6f806d4a457237ff9513aad54afe3ac44e8ba70 100644
--- a/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,10 +41,11 @@ Foam::vtkSetWriter<Type>::vtkSetWriter()
     writer<Type>()
 {}
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 template<class Type>
-Foam::vtkSetWriter<Type>::~vtkSetWriter()
+Foam::vtkSetWriter<Type>::vtkSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -111,6 +112,7 @@ template<class Type>
 void Foam::vtkSetWriter<Type>::write
 (
     const bool writeTracks,
+    const List<scalarField>& times,
     const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
diff --git a/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.H b/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.H
index 570a674bfb5d2e7d4b5602836625935e80ddb971..7e2e9493a4e25650b648f0ac905e24bc31b0ef13 100644
--- a/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/vtk/vtkSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -55,7 +56,6 @@ class vtkSetWriter
 :
     public writer<Type>
 {
-
 public:
 
     //- Runtime type information
@@ -64,12 +64,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         vtkSetWriter();
 
+        //- Construct with dictionary
+        explicit vtkSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~vtkSetWriter();
+    virtual ~vtkSetWriter() = default;
 
 
     // Member Functions
@@ -91,9 +94,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/fileFormats/sampledSetWriters/writer.C b/src/fileFormats/sampledSetWriters/writer.C
index ea1423328763c6630a260d8a7cf70777f7ae6988..09a6b884594dedaf4e7c134e94a1e05c06eaada6 100644
--- a/src/fileFormats/sampledSetWriters/writer.C
+++ b/src/fileFormats/sampledSetWriters/writer.C
@@ -31,7 +31,7 @@ License
 #include "OFstream.H"
 #include "OSspecific.H"
 
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * * //
 
 template<class Type>
 Foam::autoPtr<Foam::writer<Type>> Foam::writer<Type>::New
@@ -55,6 +55,29 @@ Foam::autoPtr<Foam::writer<Type>> Foam::writer<Type>::New
 }
 
 
+template<class Type>
+Foam::autoPtr<Foam::writer<Type>> Foam::writer<Type>::New
+(
+    const word& writeType,
+    const dictionary& formatOptions
+)
+{
+    auto ctorPtr = dictConstructorTable(writeType);
+
+    if (!ctorPtr)
+    {
+        FatalErrorInLookup
+        (
+            "writer",
+            writeType,
+            *dictConstructorTablePtr_
+        ) << exit(FatalError);
+    }
+
+    return autoPtr<writer<Type>>(ctorPtr(formatOptions));
+}
+
+
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 template<class Type>
@@ -143,10 +166,8 @@ Foam::writer<Type>::writer()
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
 template<class Type>
-Foam::writer<Type>::~writer()
+Foam::writer<Type>::writer(const dictionary& dict)
 {}
 
 
diff --git a/src/fileFormats/sampledSetWriters/writer.H b/src/fileFormats/sampledSetWriters/writer.H
index 1c09a54d1da5ced16553e2d6cb6e5bb6d0c360c7..47438ff2215ba08074e351aafcc0ae6f1419465a 100644
--- a/src/fileFormats/sampledSetWriters/writer.H
+++ b/src/fileFormats/sampledSetWriters/writer.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -70,7 +71,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declaration of classes
+// Forward Declarations
 class coordSet;
 
 /*---------------------------------------------------------------------------*\
@@ -80,7 +81,6 @@ class coordSet;
 template<class Type>
 class writer
 {
-
 protected:
 
     //- Generates filename from coordSet and sampled fields
@@ -123,21 +123,42 @@ public:
             ()
         );
 
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            writer,
+            dict,
+            (
+                const dictionary& formatOptions
+            ),
+            (formatOptions)
+        );
+
 
     // Selectors
 
         //- Return a reference to the selected writer
         static autoPtr<writer> New(const word& writeFormat);
 
+        //- Return a reference to the selected writer
+        static autoPtr<writer> New
+        (
+            const word& writeFormat,
+            const dictionary& formatOptions
+        );
+
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         writer();
 
+        //- Construct with dictionary
+        explicit writer(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~writer() = 0;
+    virtual ~writer() = default;
 
 
     // Member Functions
@@ -178,9 +199,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const = 0;
 
diff --git a/src/fileFormats/sampledSetWriters/writers.C b/src/fileFormats/sampledSetWriters/writers.C
index a730aae834347ffc0798174358c46affc35d0920..c94fc450cef854936411647d8d61daafcbb78e1b 100644
--- a/src/fileFormats/sampledSetWriters/writers.C
+++ b/src/fileFormats/sampledSetWriters/writers.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -36,7 +37,8 @@ namespace Foam
 
 #define defineSetWriterType(dataType)                                          \
     defineNamedTemplateTypeNameAndDebug(writer<dataType >, 0);                 \
-    defineTemplatedRunTimeSelectionTable(writer, word, dataType);
+    defineTemplatedRunTimeSelectionTable(writer, word, dataType);              \
+    defineTemplatedRunTimeSelectionTable(writer, dict, dataType);
 
 defineSetWriterType(scalar);
 defineSetWriterType(vector);
diff --git a/src/fileFormats/sampledSetWriters/writers.H b/src/fileFormats/sampledSetWriters/writers.H
index f931e523cbbc78397af5adcfe615ca71bb812a71..c1e7f75a7644e5976cc42edea9a63d6d76d1227d 100644
--- a/src/fileFormats/sampledSetWriters/writers.H
+++ b/src/fileFormats/sampledSetWriters/writers.H
@@ -61,7 +61,11 @@ SourceFiles
     addTemplatedToRunTimeSelectionTable                                        \
     (                                                                          \
         writer, typeWriter, dataType, word                                     \
-    )
+    );                                                                         \
+    addTemplatedToRunTimeSelectionTable                                        \
+    (                                                                          \
+        writer, typeWriter, dataType, dict                                     \
+    );
 
 
 // Define type info for scalar, vector etc. instantiations
diff --git a/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.C b/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.C
index abc6c094b06859a1bc8ad05585cab4bfe06e84bf..6faca89cffc48383b18f61560fbfa4d13d6db4f4 100644
--- a/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.C
+++ b/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,10 +41,10 @@ Foam::xmgraceSetWriter<Type>::xmgraceSetWriter()
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
 template<class Type>
-Foam::xmgraceSetWriter<Type>::~xmgraceSetWriter()
+Foam::xmgraceSetWriter<Type>::xmgraceSetWriter(const dictionary& dict)
+:
+    writer<Type>(dict)
 {}
 
 
@@ -91,7 +92,8 @@ template<class Type>
 void Foam::xmgraceSetWriter<Type>::write
 (
     const bool writeTracks,
-    const PtrList<coordSet>& trackPoints,
+    const List<scalarField>& times,
+    const PtrList<coordSet>& tracks,
     const wordList& valueSetNames,
     const List<List<Field<Type>>>& valueSets,
     Ostream& os
@@ -104,24 +106,24 @@ void Foam::xmgraceSetWriter<Type>::write
             << "Number of valueSets:" << valueSets.size()
             << exit(FatalError);
     }
-    if (trackPoints.size() > 0)
+    if (tracks.size() > 0)
     {
         os  << "@g0 on" << nl
             << "@with g0" << nl
-            << "@    title \"" << trackPoints[0].name() << '"' << nl
-            << "@    xaxis label " << '"' << trackPoints[0].axis() << '"' << nl;
+            << "@    title \"" << tracks[0].name() << '"' << nl
+            << "@    xaxis label " << '"' << tracks[0].axis() << '"' << nl;
 
         // Data index.
         label sI = 0;
 
-        forAll(trackPoints, trackI)
+        forAll(tracks, trackI)
         {
             forAll(valueSets, i)
             {
                 os  << "@    s" << sI << " legend " << '"'
                     << valueSetNames[i] << "_track" << i << '"' << nl
                     << "@target G0.S" << sI << nl;
-                this->writeTable(trackPoints[trackI], valueSets[i][trackI], os);
+                this->writeTable(tracks[trackI], valueSets[i][trackI], os);
                 os  << '&' << nl;
 
                 sI++;
diff --git a/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.H b/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.H
index 2c92aa4be1a7b0a8f26f0ca6013f3f5be39f342b..d3f064106d43d1348e9419dddb26a687d8a62cc4 100644
--- a/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.H
+++ b/src/fileFormats/sampledSetWriters/xmgrace/xmgraceSetWriter.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -52,7 +53,6 @@ class xmgraceSetWriter
 :
     public writer<Type>
 {
-
 public:
 
     //- Runtime type information
@@ -61,12 +61,15 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         xmgraceSetWriter();
 
+        //- Construct with dictionary
+        explicit xmgraceSetWriter(const dictionary& dict);
+
 
     //- Destructor
-    virtual ~xmgraceSetWriter();
+    virtual ~xmgraceSetWriter() = default;
 
 
     // Member Functions
@@ -88,9 +91,10 @@ public:
         virtual void write
         (
             const bool writeTracks,
-            const PtrList<coordSet>&,
+            const List<scalarField>& times,
+            const PtrList<coordSet>& tracks,
             const wordList& valueSetNames,
-            const List<List<Field<Type>>>&,
+            const List<List<Field<Type>>>& valueSets,
             Ostream&
         ) const;
 };
diff --git a/src/functionObjects/field/streamLine/streamLineBase.C b/src/functionObjects/field/streamLine/streamLineBase.C
index e763f5bd72da75278bad86991d9d7fea6b6aca9e..efd37d24fad975fd8961b27f2333966e939246ef 100644
--- a/src/functionObjects/field/streamLine/streamLineBase.C
+++ b/src/functionObjects/field/streamLine/streamLineBase.C
@@ -742,7 +742,8 @@ bool Foam::functionObjects::streamLineBase::writeToFile()
 
             scalarFormatterPtr_().write
             (
-                true,           // writeTracks
+                true,                   // writeTracks
+                List<scalarField>(),    // times
                 tracks,
                 scalarNames_,
                 scalarValues,
@@ -782,7 +783,8 @@ bool Foam::functionObjects::streamLineBase::writeToFile()
 
             vectorFormatterPtr_().write
             (
-                true,           // writeTracks
+                true,                   // writeTracks
+                List<scalarField>(),    // times
                 tracks,
                 vectorNames_,
                 vectorValues,
diff --git a/src/lagrangian/basic/Cloud/Cloud.H b/src/lagrangian/basic/Cloud/Cloud.H
index 2fdb8fecf41f7855a0763cdf9db0f268a7cbdfb8..06b5d9a926972114873b0f85556d755ff9277a07 100644
--- a/src/lagrangian/basic/Cloud/Cloud.H
+++ b/src/lagrangian/basic/Cloud/Cloud.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -45,6 +45,7 @@ SourceFiles
 #include "CompactIOField.H"
 #include "polyMesh.H"
 #include "bitSet.H"
+#include "wordRes.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -240,6 +241,21 @@ public:
                 const CompactIOField<Field<DataType>, DataType>& data
             ) const;
 
+            //- Helper function to store a cloud field on its registry
+            template<class Type>
+            bool readStoreFile
+            (
+                const IOobject& io,
+                const IOobject& ioNew
+            ) const;
+
+            //- Read from files into objectRegistry
+            void readFromFiles
+            (
+                objectRegistry& obr,
+                const wordRes& selectFields
+            ) const;
+
 
         // Write
 
diff --git a/src/lagrangian/basic/Cloud/CloudIO.C b/src/lagrangian/basic/Cloud/CloudIO.C
index 858565aff7b348c670fb4d695e6cf9a18706ef3a..5a34a2891ee1d9584db3e2a3e67ff7eae82ee63c 100644
--- a/src/lagrangian/basic/Cloud/CloudIO.C
+++ b/src/lagrangian/basic/Cloud/CloudIO.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017, 2020 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,6 +30,7 @@ License
 #include "Time.H"
 #include "IOPosition.H"
 #include "IOdictionary.H"
+#include "IOobjectList.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -243,6 +244,80 @@ void Foam::Cloud<ParticleType>::checkFieldFieldIOobject
 }
 
 
+template<class ParticleType>
+template<class Type>
+bool Foam::Cloud<ParticleType>::readStoreFile
+(
+    const IOobject& io,
+    const IOobject& ioNew
+) const
+{
+    if (io.headerClassName() == IOField<Type>::typeName)
+    {
+        IOField<Type> fld(io);
+        auto* fldNewPtr = new IOField<Type>(ioNew, std::move(fld));
+        return fldNewPtr->store();
+    }
+
+    return false;
+}
+
+
+template<class ParticleType>
+void Foam::Cloud<ParticleType>::readFromFiles
+(
+    objectRegistry& obr,
+    const wordRes& selectFields
+) const
+{
+    IOobjectList cloudObjects
+    (
+        *this,
+        time().timeName(),
+        "",
+        IOobject::MUST_READ,
+        IOobject::NO_WRITE,
+        false
+    );
+
+    forAllIters(cloudObjects, iter)
+    {
+        if (selectFields.size() && !selectFields.match(iter()->name()))
+        {
+            continue;
+        }
+
+        IOobject ioNew
+        (
+            iter()->name(),
+            time().timeName(),
+            obr,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        );
+
+        auto& object = *iter();
+
+        const bool stored
+        (
+            readStoreFile<label>(object, ioNew)
+         || readStoreFile<scalar>(object, ioNew)
+         || readStoreFile<vector>(object, ioNew)
+         || readStoreFile<sphericalTensor>(object, ioNew)
+         || readStoreFile<symmTensor>(object, ioNew)
+         || readStoreFile<tensor>(object, ioNew)
+        );
+
+        if (!stored)
+        {
+            DebugInfo
+                << "Unhandled field type " << iter()->headerClassName()
+                << endl;
+        }
+    }
+}
+
+
 template<class ParticleType>
 void Foam::Cloud<ParticleType>::writeFields() const
 {
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
index 5be91fd952cd5df2787bd04c1182db42f6f48be9..3c19ea7087253d67716b5d5f83d72362c8092386 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
@@ -2700,7 +2700,8 @@ Foam::label Foam::meshRefinement::findRegions
                     {
                         leakPathFormatter.write
                         (
-                            true,               // write tracks
+                            true,                // write tracks
+                            List<scalarField>(), // times
                             allLeakPaths,
                             valueSetNames,
                             allLeakData,
diff --git a/src/sampling/sampledSet/sampledSets/sampledSets.C b/src/sampling/sampledSet/sampledSets/sampledSets.C
index 2420bc7ac5927915140f3fb9e66e15817d40998c..06601d251e3321974336bb289e0d8ce01bc0d708 100644
--- a/src/sampling/sampledSet/sampledSets/sampledSets.C
+++ b/src/sampling/sampledSet/sampledSets/sampledSets.C
@@ -97,7 +97,8 @@ Foam::sampledSets::sampledSets
     outputPath_(fileName::null),
     searchEngine_(mesh_),
     interpolationScheme_(word::null),
-    writeFormat_(word::null)
+    writeFormat_(word::null),
+    writeFormatOptions_(dict.subOrEmptyDict("formatOptions"))
 {
     outputPath_ =
     (
@@ -106,7 +107,7 @@ Foam::sampledSets::sampledSets
 
     if (mesh_.name() != polyMesh::defaultRegion)
     {
-        outputPath_ = outputPath_/mesh_.name();
+        outputPath_ /= mesh_.name();
     }
 
     outputPath_.clean();  // Remove unneeded ".."
@@ -130,7 +131,8 @@ Foam::sampledSets::sampledSets
     outputPath_(fileName::null),
     searchEngine_(mesh_),
     interpolationScheme_(word::null),
-    writeFormat_(word::null)
+    writeFormat_(word::null),
+    writeFormatOptions_(dict.subOrEmptyDict("formatOptions"))
 {
     outputPath_ =
     (
@@ -139,7 +141,7 @@ Foam::sampledSets::sampledSets
 
     if (mesh_.name() != polyMesh::defaultRegion)
     {
-        outputPath_ = outputPath_/mesh_.name();
+        outputPath_ /= mesh_.name();
     }
 
     outputPath_.clean();  // Remove unneeded ".."
diff --git a/src/sampling/sampledSet/sampledSets/sampledSets.H b/src/sampling/sampledSet/sampledSets/sampledSets.H
index 0f38753b7d2d183451660242af26714e947eed90..d148f48514716cc041eb85689a72de95970a0ad1 100644
--- a/src/sampling/sampledSet/sampledSets/sampledSets.H
+++ b/src/sampling/sampledSet/sampledSets/sampledSets.H
@@ -53,7 +53,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class Time;
 class objectRegistry;
 class dictionary;
@@ -68,7 +68,7 @@ class sampledSets
     public functionObjects::regionFunctionObject,
     public PtrList<sampledSet>
 {
-    // Private classes
+    // Private Classes
 
         //- Class used for grouping field types
         template<class Type>
@@ -82,18 +82,7 @@ class sampledSets
             autoPtr<writer<Type>> formatter;
 
             //- Construct null
-            fieldGroup()
-            :
-                DynamicList<word>(0),
-                formatter(nullptr)
-            {}
-
-            //- Construct for a particular format
-            fieldGroup(const word& writeFormat)
-            :
-                DynamicList<word>(0),
-                formatter(writer<Type>::New(writeFormat))
-            {}
+            fieldGroup() = default;
 
             //- Reset format and field list
             void clear()
@@ -102,10 +91,9 @@ class sampledSets
                 formatter.clear();
             }
 
-            //- Assign a new formatter
-            void operator=(const word& writeFormat)
+            void setFormatter(const word& writeFormat, const dictionary& dict)
             {
-                formatter = writer<Type>::New(writeFormat);
+                formatter = writer<Type>::New(writeFormat, dict);
             }
         };
 
@@ -151,13 +139,13 @@ class sampledSets
         };
 
 
-    // Static data members
+    // Static Data Members
 
         //- Output verbosity
         static bool verbose_;
 
 
-    // Private data
+    // Private Data
 
         //- Const reference to fvMesh
         const fvMesh& mesh_;
@@ -175,7 +163,7 @@ class sampledSets
         meshSearch searchEngine_;
 
 
-      // Read from dictonary
+    // Read from dictionary
 
         //- Names of fields to sample
         wordRes fieldSelection_;
@@ -186,8 +174,11 @@ class sampledSets
         //- Output format to use
         word writeFormat_;
 
+        //- Dictionary containing writer options
+        dictionary writeFormatOptions_;
 
-      // Categorized scalar/vector/tensor fields
+
+    // Categorized scalar/vector/tensor fields
 
         fieldGroup<scalar> scalarFields_;
         fieldGroup<vector> vectorFields_;
@@ -196,7 +187,7 @@ class sampledSets
         fieldGroup<tensor> tensorFields_;
 
 
-      // Merging structures
+    // Merging structures
 
         PtrList<coordSet> masterSampledSets_;
         labelListList indexSets_;
@@ -211,7 +202,7 @@ class sampledSets
         label classifyFields();
 
         //- Combine points from all processors. Sort by curveDist and produce
-        //  index list. Valid result only on master processor.
+        //- index list. Valid result only on master processor.
         void combineSampledSets
         (
             PtrList<coordSet>& masterSampledSets,
diff --git a/src/sampling/sampledSet/sampledSets/sampledSetsTemplates.C b/src/sampling/sampledSet/sampledSets/sampledSetsTemplates.C
index 82dad524a242f8197f983656b123f3a7464a7f64..c9441dd28c1c4cd7b7855d8c5a5da149007776da 100644
--- a/src/sampling/sampledSet/sampledSets/sampledSetsTemplates.C
+++ b/src/sampling/sampledSet/sampledSets/sampledSetsTemplates.C
@@ -231,7 +231,7 @@ void Foam::sampledSets::sampleAndWrite(fieldGroup<Type>& fields)
         // Create or use existing writer
         if (!fields.formatter)
         {
-            fields = writeFormat_;
+            fields.setFormatter(writeFormat_, writeFormatOptions_);
         }
 
         // Storage for interpolated values
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties
index 5504a76f344a4c6e17dd1e2ea5e9ace705e84770..e70b4312de96d8446254db03b627d663f4cad7ee 100644
--- a/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -20,5 +20,32 @@ sampleFrequency 1;
 
 maxPositions    1000000;
 
+//maxTracks       5;
+
+setFormat       gltf;
+
+formatOptions
+{
+    animate         yes;
+    colour          yes;
+
+    animationInfo
+    {
+        colour          field;
+        colourField     d;
+        //min             0;
+        //max             0.002;
+
+        //alpha           uniform;
+        //alphaValue      1;
+
+        alpha           field;
+        alphaField      d;
+        normalise       yes;
+    }
+}
+
+fields          (d);
+
 
 // ************************************************************************* //
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.animate b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.animate
new file mode 100644
index 0000000000000000000000000000000000000000..e70b4312de96d8446254db03b627d663f4cad7ee
--- /dev/null
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.animate
@@ -0,0 +1,51 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2112                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      particleTrackProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+cloud           reactingCloud1;
+
+sampleFrequency 1;
+
+maxPositions    1000000;
+
+//maxTracks       5;
+
+setFormat       gltf;
+
+formatOptions
+{
+    animate         yes;
+    colour          yes;
+
+    animationInfo
+    {
+        colour          field;
+        colourField     d;
+        //min             0;
+        //max             0.002;
+
+        //alpha           uniform;
+        //alphaValue      1;
+
+        alpha           field;
+        alphaField      d;
+        normalise       yes;
+    }
+}
+
+fields          (d);
+
+
+// ************************************************************************* //
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.static b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.static
new file mode 100644
index 0000000000000000000000000000000000000000..76b844fff6e585a38a3155b3d0fb8c25092f9672
--- /dev/null
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/constant/particleTrackProperties.static
@@ -0,0 +1,51 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2112                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      particleTrackProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+cloud           reactingCloud1;
+
+sampleFrequency 1;
+
+maxPositions    1000000;
+
+//maxTracks       5;
+
+setFormat       gltf;
+
+formatOptions
+{
+    animate         no;
+    colour          yes;
+
+    fieldInfo
+    {
+        d
+        {
+            colourMap       rainbow;
+            min             0;
+            max             0.001;
+
+            alpha           field; // uniform | field;
+            //alphaValue      0.1; // uniform alpha value
+            alphaField      d;
+            normalise       yes;
+        }
+    }
+}
+
+fields          (d);
+
+
+// ************************************************************************* //
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict b/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
index c196a53845cab42872d6e054221fb0d190cd3d88..aace3d5302d88c167053d33516896ff7280450e9 100644
--- a/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/system/controlDict
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -52,6 +52,7 @@ maxDeltaT       1;
 
 functions
 {
+    #include "sample"
     #include "dataCloud"
     #include "vtkCloud"
     #include "vtkWrite"
diff --git a/tutorials/lagrangian/reactingParcelFoam/filter/system/sample b/tutorials/lagrangian/reactingParcelFoam/filter/system/sample
new file mode 100644
index 0000000000000000000000000000000000000000..52fecdea3d3cf50f36a5536680a7cc8c06a2a540
--- /dev/null
+++ b/tutorials/lagrangian/reactingParcelFoam/filter/system/sample
@@ -0,0 +1,52 @@
+// -*- C++ -*-
+
+sample1
+{
+    type        sets;
+    libs        (sampling);
+    setFormat   gltf;
+
+    interpolationScheme cellPointFace;
+
+    formatOptions
+    {
+        // Optionally add colours to fields
+        // - default: colour map limits set to field limits
+        colour          yes;
+
+        fieldInfo
+        {
+            T
+            {
+                colourMap       fire;
+
+                alpha           field; // uniform | field;
+                //alphaValue      0.1; // uniform alpha value
+                alphaField      T;
+                normalise       yes;
+            }
+        }
+    }
+
+    fields          ( p T k epsilon U );
+    writeControl    writeTime;
+
+    sets
+    (
+        line
+        {
+            type            face;
+            axis            xyz;
+            start           (0 0.5 0);
+            end             (4 0.5 0.05);
+            nPoints         10;
+        }
+        cells
+        {
+            type            cellCentre;
+        }
+    );
+}
+
+
+// ************************************************************************* //