Skip to content

PLY Binary IO: binary write support and header-declared endianness on read #25

Description

@csparker247

Track: ply-binary-io_20260904

write_ply emits format ascii 1.0 unconditionally. For a multi-million-face mesh that is several times the size of the binary equivalent and correspondingly slower to write — pgs-decimate writes its output once per improved search round and pays that repeatedly. Separately, read_ply supports binary_little_endian but does not actually honor the header's declared endianness: read_ply_binary_prop and read_ply_prop_from_buf reinterpret raw bytes as native types, so it is native-endian reading labeled little-endian, and binary_big_endian is rejected outright.

These are one contract and land together: a writer that can emit binary must not be able to produce a file the reader refuses or silently misreads.

Scope

Write

  • Public educelab::PLYFormat { ASCII, Binary }, threaded through all three write_ply tiers. Kept separate from detail::PLYHeader::Format (three values, "what I parsed") rather than leaking a detail type into a public signature.
  • Default stays ASCII, so existing callers and header-grepping tests are unaffected; pgs-decimate opts in.
  • Binary writes native-endian and labels the header accordingly.
  • Scalars always written as float32 regardless of the mesh's T, matching what the header already declares in ASCII and what OpenMVS emits. Avoids the on-disk format depending on a template parameter.
  • Mirror the reader's record batching — precomputed property offsets, one write per vertex — rather than one write per property.
  • Open all three tiers with std::ios::binary unconditionally. Note: this is a silent change for Windows ASCII callers, who stop getting CRLF. read_ply already trims \r, so nothing regresses on read.

Read

  • Honor the endianness declared in the header, byte-swapping when file order differs from host order. Removes the binary_big_endian rejection.
  • Swap enters at the two choke points every binary scalar read passes through: read_ply_binary_prop (stream) and read_ply_prop_from_buf (buffer).
  • Runtime bool rather than a template parameter: the flag is loop-invariant so the branch predicts perfectly, and templating doubles instantiated code for no gain.
  • The swap must apply to the raw fixed-width value before the cast to the destination type. Converting first and reversing afterwards yields a valid number that is the wrong one — BE 3F 80 00 00 memcpy'd into a native float is 4.6e-41, and no reversal of the widened double recovers 1.0.

Validation

  • Throw when a face exceeds the uchar list-count limits. Two distinct limits: 255 corners for vertex_indices, and 127 for texcoord, since that list writes 2*N. Declarations stay uchar — widening to uint32 would cost 3 bytes per face (~30MB on a 10M-face mesh) to guard a case that does not occur.
  • Message names which limit fired and the offending face index. @throws documented on the tier-2/tier-3 write_ply overloads and the corresponding write_mesh dispatchers; tier 1 carries only the 255 limit.

Testing

Round-trip tests cannot anchor this work — the sized-alias bug in #24 survived precisely because the suite only ever read what libcore wrote, and a byte-order mistake shared by reader and writer round-trips just as happily.

  • Reader: a hand-crafted binary_big_endian fixture with bytes reversed by the test, exercising the swap path on an LE host.
  • Writer: byte-level assertion of a small mesh's binary body against a hand-derived literal, so the test encodes the format rather than libcore's opinion of it. Round-trip used only for structural breadth (n-gons, UVs, colors) where hand-computing bytes stops paying.

Sequencing

Lands before #19 (Multi-Chart PLY Write Support). Both rewrite write_ply_header and write_ply_data; #19 is Pending at 0/15, this is the live need, and it is the smaller and more additive of the two.

Depends on #24.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions