Some info on SharedStrings for custom collision data (MeshParts, Unions, etc.)

(The section about manipulating data has been removed. This is so that people arent encouraged to edit this data. do not edit this data unless you’re prepared to have future updates possibly breaking your games!)

CSGPHS format.

This format can change at any time and any tooling you develop around this format may break suddenly in the future without warning.

The first 6 bytes spell out CSGPHS.

char magic[6]; // spells out CSGPHS

The next 4 bytes indicates the type of collision mesh.

uint32_t type;

From my testing, I know of only two possible formats: 0 indicates that this uses the box collisions (when CollisionFidelity is set to Box) and 6 indicates that this uses convex hulls (when CollisionFidelity is set to Hull or Default).

Fun fact: If you export a MeshPart which has its CollisionFidelity set to Default as a file and it only has one convex hull when you view its decomposition geometry, it will appear with its CollisionFidelity set to Hull when you re-import it. This tells us that the CollisionFidelity isn’t serialized with the file, but inferred from CSGPHS.

Type=0, Box

After parsing the type, 5 characters will follow. These characters spell out BLOCK.

char block[5]; // spells out BLOCK

… and that’s all she wrote for this type.

Type=6, Default and Hull

This type is more complex. It can be split into two parts: Physics Data and Mesh Data.

Physics Data

After parsing the type, the unscaled volume of the collision mesh follows (note: all numbers are relative to the unscaled mesh). This is stored as a float.

float volume;

After volume, the mesh’s center of gravity is described. This consists of three floats.

float cog_x, cog_y, cog_z;

The last data before the actual collision mesh(es) are described is the mesh’s inertia tensor. This is important for physics involving rotations. I think (!) that it’s stored in the following order:
image

float im_xx, im_xy, im_xz, im_yy, im_yz, im_zz;

(I’m only sure of where 1, 4, and 6 are. I’m just assuming where 2, 3, and 5 are based on what makes sense. I didn’t do enough testing to make sure.)

Mesh Data

Immediately after the physics data is the mesh data. This data is encoded for every convex hull in the mesh.

The first 4 bytes (and the following 16 bytes) is… trash? I don’t know what this part means exactly, but the 4 bytes are always an int with a value of 16 and the 16 bytes after that are all 0.

uint32_t trash1;
char trash1_dump[16]; // {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}

After that is… more trash? It has the same format as the first trash, but instead of the 16 bytes being all 0, the final two bytes are 0x80 and 0x3f:

uint32_t trash2;
char trash2_dump[16]; // {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80,0x3f}

After these two trash chunks is the useful data. The next 4 bytes is the number of floats that describe the vertex data.

uint32_t verts_size;

vert_size/3 is basically the number of vertices in the mesh.

The next 4 bytes after that is (what I think is) the size of each component in a vertex. Since floats are 4 bytes, this value is 4.

uint32_t vert_width; // 4 in all the meshes I've tested.

After this is an array of <verts_size> floats. Each vertex is made up of three components that describe its position: x, y, and z. If there are v_0, v_1 ... v_n vertices in the mesh, then in memory they will be ordered as x_0, y_0, z_0, x_1, y_1, z_1 ... x_n, y_n, z_n. A more familiar way of putting this is that each vertex is a Vector3 struct:

struct Vector3
{
    float x, y, z;
}
// ...
Vector3* verts = new Vector3[verts_size/3];

After the vertex data comes the face data. 4 bytes indicate the number of ints that describe the faces.

uint32_t faces_size

Since each face is a triangle, faces_size/3 is basically the number of faces in the mesh.

Finally, the rest of the data (for the current convex hull) is an array of <faces_size> 4-byte ints. A face is described by three vertices. Each int is basically an index into the verts array. You’d be familiar with this scheme if you’ve processed mesh files before.

struct Face
{
    uint32_t a, b, c;
}
// ...
Face* faces = new Face[faces_size/3];

… Tada! And that’s it for hull/default-type collision data.

42 Likes

That’s actually pretty cool! A nice finding indeed.

This is so awesome, thank you for sharing it
Does publishing the place keep the [modified] SharedStrings?
I just want to clarify, is the mesh data solely for collisions, not visuals? And is it possible to create our own non-hull mesh–or just use the actual rendered mesh–for collisions?
Why don’t the modified cube and the cylinder roll the exact same in your ramp video?

Edit: Also it seems like I am just way behind on my wiki reading, is there any particular way you find the new articles?

Does publishing the place keep the [modified] SharedStrings?

Yes. When you publish the place, the modified SharedStrings are saved.

I just want to clarify, is the mesh data solely for collisions, not visuals?

The data encoded in the SharedString not just visual (as you can tell by the video of the cylinder and cube rolling down the slope). Volume, center of gravity, and moments of inertia is saved, too.

And is it possible to create our own non-hull mesh–or just use the actual rendered mesh–for collisions?

I don’t know if non-hull meshes work. I haven’t tested that yet. But I’m guessing that either the resulting physics will be incorrect or the engine will force the mesh to be a hull.

Why don’t the modified cube and the cylinder roll the exact same in your ramp video?

I don’t know. I used the dragger tool to position both. Here’s two of the exact same cylinder rolling down the slope. You can see that they deviate, too.

Also it seems like I am just way behind on my wiki reading, is there any particular way you find the new articles?

New articles on what?

1 Like

Grear work figuring this out! A few things to note:

“Trash data” usually implies some sort of reserved space for future extensions to the format, though it seems a bit odd they would reserve that much data. It might be worth looking into a little more.

When I was figuring out version 3.00 of Roblox’s mesh format, I messed around with unknown bytes to see if any of them were specifying the sizeof certain structs.

4 Likes

The data encoded in the SharedString not just visual

Wait wait, by “not just” does it mean it has some sort of effect on visuals? What is it?

New articles on what?

Well maybe not so new, but I’d never seen the Roblox Mesh Format article before

I misspoke. It has no effect on visuals. It’s purely physics.

Google “Roblox Mesh Format”. That’s how I found it.

1 Like

Holy heck I would not write anything depending on this functionality or format… seems like it’s not meant for fiddling around with.

1 Like

:ok_hand: That’s a valid concern.

I should add a disclaimer that this format can change at any time and any tooling you develop around this format may break suddenly in the future without warning.

1 Like

I’m not particularly against anyone knowing about this format, but be sure to know that as we add new features your changes may randomly stop working, or be overwritten (In game, not just Studio)

7 Likes

PLEASE READ:
Please please please don’t mess with these objects. Currently we are in the middle of slowly migrating from one data format to another. There is an old property that used to store Collision Geometry as a Binary String with the help of something called Dictionary Service.

I have a bunch of migration logic currently running on our servers that Identify “migrated” and “unmigrated” objects based on where physics data currently resides. If you start adding things to this property, while the old property is around you may forever leave your objects with a bunch of useless memory that won’t be cleaned up.

TLDR:

  • Please don’t tinker with this.
  • Please don’t tinker with this at this time, specifically, because we are migrating a bunch of data and randomly edited properties will break some assumptions the system is making which may result with your games polluted with trash in memory.

About the “Trash” bits

If you’re curious about some of the “trash”, there are 2 things:

  1. Triangle Indices
  2. Transform Offsets for Sub Shapes.

Currently neither of those are important for the current formulation of collision detection data, however that doesn’t mean they won’t be important in the future.

The Transform offsets probably won’t be used because they can be redundantly replaced with just offsetting the position of vertices of each shape.

The indices are currently not used, but I have a special branch that I was experimenting with that was using indices to accelerate collision detection through GJK (algorithm common for Convex-Convex). So be careful what you call trash, because if we suddenly start relying on that data you may create “corrupt” shapes.

4 Likes

I figured this might be the case. BinaryString properties in general tend to be quite volatile, and there hasn’t been a lot of consideration put into whether people are tinkering with Roblox’s file formats. 3rd-party libraries for such are only now starting to emerge.

In the future I hope this will be kept in mind, as there are a lot of power users (myself included) who are interested in building solutions to problems that Roblox hasn’t (or likely won’t) provide solutions for anytime in the near future.

I already had to tinker with both Roblox’s file format and mesh format to efficiently mass-generate bevel MeshParts in Super Nostalgia Zone.

image

That isn’t to say I’m demanding that mesh/union data be easy to modify externally, of course not.

Really I think it comes down to a case-by-case basis. In my case, the mesh format is versioned, and doesn’t force any migration to new features unless you opt into them, so I felt like it was something I could feel safe automating to my needs.

For something like this, the data is obviously volatile and custom data may not migrate forward cleanly. I don’t think anyone is actually using custom physics data on production, but if someone was technical and desperate enough to have a specific collision mesh tied to a single MeshPart, I wouldn’t blame them for trying.

3 Likes