Flipbook keyframe animation with individual mesh per frame

Hello all,

I have a LiDAR point cloud animation which I want to generate a USD version of. I have different particles count possible in each frame, so I want to generate per-frame points mesh that I flip through on each new frame.

I managed to generate this with an AI agent and it does seem like what I am after:

#usda 1.0
(
    defaultPrim = "World"
    endTimeCode = 2
    framesPerSecond = 1
    startTimeCode = 0
)

def Xform "World"
{
    def Points "Frame_0"
    {
        point3f[] points = [
            (-3.0, 0.0, 0.0), (-2.5, 0.2, 0.1), (-2.0, 0.0, 0.0), (-1.5, -0.1, 0.2), (-1.0, 0.0, 0.0),
            (-0.5, 0.3, -0.1), (0.0, 0.0, 0.0), (0.5, -0.2, 0.3), (1.0, 0.0, 0.0), (1.5, 0.1, -0.2),
            (2.0, 0.0, 0.0), (-2.2, 0.5, 0.4), (-0.8, -0.3, 0.1), (0.3, 0.4, -0.3), (1.8, -0.1, 0.2)
        ]
        color3f[] primvars:displayColor = [
            (1.0, 0.0, 0.0), (0.9, 0.1, 0.0), (0.8, 0.2, 0.0), (0.7, 0.3, 0.0), (0.6, 0.4, 0.0),
            (0.5, 0.5, 0.0), (1.0, 0.0, 0.2), (0.9, 0.0, 0.3), (0.8, 0.0, 0.4), (0.7, 0.0, 0.5),
            (0.6, 0.0, 0.6), (1.0, 0.2, 0.0), (0.8, 0.4, 0.0), (0.6, 0.6, 0.0), (0.9, 0.1, 0.1)
        ] (
            interpolation = "vertex"
        )
        float[] widths = [
            0.8, 1.0, 0.9, 1.2, 0.7, 1.1, 0.6, 1.3, 0.8, 0.9, 1.0, 0.7, 1.2, 0.8, 1.1
        ]
        
        token visibility.timeSamples = {
            0: "inherited",
            1: "invisible", 
            2: "invisible"
        }

        uniform token subdivisionScheme = "none"
        double3 xformOp:rotateXYZ = (0, 0, 0)
        double3 xformOp:scale = (1, 1, 1)
        double3 xformOp:translate = (0, 0, 0)
        uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
    }

    def Points "Frame_1" 
    {
        point3f[] points = [
            (-2.8, 1.2, 0.5), (-2.0, 1.8, 0.3), (-1.3, 0.9, -0.2), (-0.7, 1.5, 0.4), (0.2, 0.7, 0.1),
            (0.8, 1.3, -0.3), (1.5, 0.4, 0.6), (2.2, 1.1, -0.1), (2.7, 0.8, 0.2), (-2.5, -0.6, 0.7),
            (-1.1, 1.7, -0.4), (0.4, -0.8, 0.5), (1.7, 1.6, 0.0), (2.3, -0.3, 0.8), (0.0, 2.1, -0.2)
        ]
        color3f[] primvars:displayColor = [
            (0.0, 1.0, 0.0), (0.1, 0.9, 0.2), (0.2, 0.8, 0.4), (0.0, 0.7, 0.6), (0.3, 0.6, 0.1),
            (0.1, 0.9, 0.3), (0.0, 0.8, 0.5), (0.2, 0.7, 0.2), (0.0, 1.0, 0.1), (0.4, 0.6, 0.0),
            (0.1, 0.8, 0.4), (0.0, 0.9, 0.2), (0.3, 0.7, 0.1), (0.0, 0.6, 0.7), (0.2, 1.0, 0.0)
        ] (
            interpolation = "vertex"
        )
        float[] widths = [
            1.2, 0.8, 1.4, 0.6, 1.6, 0.9, 1.1, 1.3, 0.7, 1.5, 0.8, 1.0, 1.2, 0.9, 1.4
        ]
        
        token visibility.timeSamples = {
            0: "invisible",
            1: "inherited",
            2: "invisible"
        }

        uniform token subdivisionScheme = "none"
        double3 xformOp:rotateXYZ = (0, 0, 0)
        double3 xformOp:scale = (1, 1, 1)
        double3 xformOp:translate = (0, 0, 0)
        uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
    }

    def Points "Frame_2"
    {
        point3f[] points = [
            (-1.5, -1.8, 1.0), (-0.9, -2.3, 0.8), (-0.2, -1.4, 1.2), (0.6, -2.0, 0.6), (1.3, -1.1, 0.9),
            (1.9, -1.7, 1.1), (2.5, -0.8, 0.7), (-2.1, 0.9, -0.5), (-1.4, -0.4, 1.3), (-0.6, 0.6, -0.8),
            (0.1, -1.9, 1.4), (0.9, 0.3, -0.6), (1.6, -1.2, 1.0), (2.2, 0.7, -0.4), (-0.3, 1.8, 0.9)
        ]
        color3f[] primvars:displayColor = [
            (0.0, 0.0, 1.0), (0.2, 0.0, 0.9), (0.4, 0.0, 0.8), (0.6, 0.0, 0.7), (0.1, 0.2, 0.9),
            (0.3, 0.1, 0.8), (0.0, 0.3, 1.0), (0.2, 0.2, 0.8), (0.4, 0.0, 0.9), (0.0, 0.4, 0.7),
            (0.5, 0.1, 0.8), (0.1, 0.0, 1.0), (0.3, 0.2, 0.6), (0.0, 0.1, 0.9), (0.4, 0.3, 0.7)
        ] (
            interpolation = "vertex"
        )
        float[] widths = [
            0.9, 1.3, 0.7, 1.5, 0.8, 1.1, 1.4, 0.6, 1.2, 1.0, 0.9, 1.3, 0.8, 1.4, 1.1
        ]
        
        token visibility.timeSamples = {
            0: "invisible",
            1: "invisible",
            2: "inherited"
        }

        uniform token subdivisionScheme = "none"
        double3 xformOp:rotateXYZ = (0, 0, 0)
        double3 xformOp:scale = (1, 1, 1)
        double3 xformOp:translate = (0, 0, 0)
        uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
    }
}

So far I can play this file correctly in macOS preview. It also aligns with this sample I found online.

When I try to convert it to USDZ to send it as an iMessage for example, the file no longer plays back. I am using usdzip to create my .usdz file. My questions are:

  1. The `token visibility.timeSamples = { “visible”, “hidden” }` playback approach seems correct to me? Core glTF does not have anything like visibility.timeSamples so I am forced to move inactive frames to position (1000, 0, 0) instead in my exporter.
  2. Why would the .usdz file produced by usdzip fail opening and playing back? Can such a point cloud animation be achieved? Ideally I want to be able to play the point clouds in iMessage / macOS as I am aiming towards a more casual crowd.

Thanks in advance!