.. _file-formats-particle-page:

############################################
Supported File Formats (ParticleModel)
############################################

The PtServer supports reading of the following particle data formats out of the box:

-   PTFX: HOOPS Envision binary particle format (\*.ptfx)
-   VTP: VTK PolyData XML particle format (\*.vtp)

Additional file formats can be added at runtime through reader plugins.
See :ref:`reader-plugins-particle-page` for details.

.. contents:: On this page
   :local:
   :depth: 2


PTFX (Binary Particle Format)
==============================

PTFX is the native high-performance binary format for particle simulation data in
HOOPS Envision.  It is designed for fast sequential frame streaming and supports
an arbitrary number of scalar fields per particle.

A PTFX dataset consists of:

1. A **header file** (``<name>.ptfx``): 48-byte binary header containing global
   metadata.
2. **Per-frame binary files** (``<name>_0000.bin``, ``<name>_0001.bin``, ...)  - 
   one file per timestep.  Both 4-digit and 6-digit zero-padded indices are
   supported (auto-detected).
3. An optional **sidecar JSON** (``<name>.ptfx.json``): scalar field names and
   range metadata.


Header layout (48 bytes, little-endian)
----------------------------------------

.. list-table::
   :header-rows: 1
   :widths: 15 15 70

   * - Offset
     - Type
     - Description
   * - 0
     - char[4]
     - Signature: ``"PTFX"``
   * - 4
     - uint32
     - Format version (currently ``1``)
   * - 8
     - uint32
     - Maximum particle count across all frames
   * - 12
     - uint32
     - Timestep (frame) count
   * - 16
     - float32 x 3
     - Bounding-box minimum (x, y, z)
   * - 28
     - float32 x 3
     - Bounding-box maximum (x, y, z)
   * - 40
     - float32
     - Scalar range minimum (legacy single-field; use sidecar JSON for
       multi-field datasets)
   * - 44
     - float32
     - Scalar range maximum


Per-frame binary layout
------------------------

Each frame file (``<name>_NNNN.bin``) has:

- **First 4 bytes**: ``particleCount`` (uint32, little-endian): number of
  particles in this frame.
- **Particle records** (array-of-structures): one record per particle, each
  record containing:

  .. list-table::
     :header-rows: 1
     :widths: 20 20 60

     * - Field
       - Type
       - Description
     * - id
       - uint32
       - Unique particle identifier (persistent across frames)
     * - x
       - float32
       - X position
     * - y
       - float32
       - Y position
     * - z
       - float32
       - Z position
     * - scalar0 ... scalarn
       - float32 each
       - One value per scalar field defined in the sidecar JSON

  Total bytes per particle: ``16 + 4 x scalarFieldCount``.


Sidecar JSON
-------------

An optional JSON file alongside the header provides multi-scalar metadata:

.. code-block:: json

   {
     "scalarFields": [
       { "name": "Temperature", "min": 290.0, "max": 1200.0 },
       { "name": "Velocity",    "min": 0.0,   "max": 35.5 }
     ]
   }

If the sidecar is absent, the reader falls back to a single scalar field named
"Scalar" with the min/max stored in the header.


Writing PTFX data
------------------

The ``PtfxDatasetWriter`` class in the desktop SDK (``CeeParticleModel`` library)
provides a simple API for converting proprietary particle data to PTFX:

1. ``open(path, maxParticles, frameCount, bboxMin, bboxMax, scalarFields)``
2. ``writeFrame(ids, positions, scalarArrays)``: call once per timestep
3. ``close()``: finalizes the header with observed ranges

See the :doc:`/guide/server-particle-model` getting-started section for a complete
workflow, and the PTFX converter example in the desktop SDK documentation for a
step-by-step tutorial.


VTP (VTK PolyData XML)
=======================

The PtServer supports reading time-series particle data stored as a sequence of
VTK PolyData XML files (``.vtp``).  One ``.vtp`` file represents one frame;
files are sorted alphabetically to determine frame order (e.g.
``particles_0000.vtp``, ``particles_0001.vtp``, ...).


Supported subset
-----------------

.. list-table::
   :header-rows: 1
   :widths: 30 70

   * - Feature
     - Support
   * - Encoding
     - **ASCII only**.  Binary, appended, and base64-encoded VTP files are not
       supported.
   * - Points
     - ``<DataArray NumberOfComponents="3">`` inside ``<Points>``, interleaved
       XYZ (float).
   * - Particle count
     - Taken from ``<Piece NumberOfPoints="N">``.
   * - Particle IDs
     - A ``<DataArray>`` named ``"Id"`` (case-insensitive) in ``<PointData>``.
       If absent, sequential IDs ``[0, 1, 2, ...]`` are generated automatically.
   * - Scalar fields
     - All single-component ``<DataArray>`` elements in ``<PointData>`` are
       treated as scalar fields, **excluding** the ID array.
   * - Active scalar
     - Determined by the ``Scalars`` attribute on the ``<PointData>`` element.
       If unset, the first scalar field is activated by default.

.. note::

   Only the subset above is read by the PtServer.  Advanced VTP features such as
   cell data, connectivity, strips, or multi-component vector arrays are ignored.


Example VTP file
-----------------

.. code-block:: xml

   <?xml version="1.0"?>
   <VTKFile type="PolyData" version="0.1" byte_order="LittleEndian">
     <PolyData>
       <Piece NumberOfPoints="3" NumberOfVerts="0" NumberOfLines="0"
              NumberOfStrips="0" NumberOfPolys="0">
         <PointData Scalars="Temperature">
           <DataArray type="Float32" Name="Id" NumberOfComponents="1"
                      format="ascii">
             0 1 2
           </DataArray>
           <DataArray type="Float32" Name="Temperature" NumberOfComponents="1"
                      format="ascii">
             300.0 450.5 1100.2
           </DataArray>
         </PointData>
         <Points>
           <DataArray type="Float32" NumberOfComponents="3" format="ascii">
             0.0 0.0 0.0
             1.0 2.0 0.5
             3.0 1.0 1.5
           </DataArray>
         </Points>
       </Piece>
     </PolyData>
   </VTKFile>


Custom Formats via Reader Plugins
==================================

Arbitrary particle file formats can be supported by implementing a reader plugin
and placing it in the folder specified by the ``CEW_PT_READER_PLUGIN_FOLDER``
environment variable.  Plugins follow the naming convention
``cpt_{NAME}.dll`` / ``.so`` / ``.dylib`` and are discovered automatically at
server startup.

For a complete guide on developing reader plugins: including the C++ ABI,
discovery mechanism, and desktop SDK integration: see
:ref:`reader-plugins-particle-page`.
