Files

338 lines
8.6 KiB
Markdown
Raw Permalink Normal View History

2026-05-14 17:18:38 +03:00
# CAniMesh / MSH Loading and Joint Bounds — Key Summary
## New findings discovered in this chat
* `tag` is best understood as a runtime **loaded submodel tag** for pieces created from one `.msh` load call.
* `tag == 0` is the primary/root `.msh` model.
* `tag != 0` means an attached/additional `.msh` whose source node `0` is skipped and used as a virtual attach root.
* One `CAniMesh` can aggregate multiple `.msh` resources into a single flat `pieces_vector`.
* Attached `.msh` pieces are not kept as separate models at runtime; their nodes are remapped into absolute `CAniMesh::pieces_vector` indices.
* `attach_parent_absolute_piece_index` is an absolute index in `CAniMesh::pieces_vector`, not a local index inside the attached `.msh`.
* The first runtime piece created by each `.msh` load should be marked as a submodel/subtree root.
* `MSH_PIECE_FLAG_TAG_ROOT` should be renamed to `MSH_PIECE_FLAG_SUBMODEL_ROOT` or `MSH_PIECE_FLAG_LOADED_SUBTREE_ROOT`.
* `ComputeJointBoundingBox` is the authoritative recursive joint/subtree bounds function.
* `ComputeJointBoundingSphere` uses specialized fast paths for single-piece and root whole-mesh bounds, but for non-root subtree bounds it delegates to `ComputeJointBoundingBox` and wraps the resulting AABB in a sphere.
* Geometry-less pieces are valid helper/socket joints: their bounds become a point or zero-radius sphere at the joint transform origin.
* The renamed filter `g_mesh_filter_only_subtree_and_exclude_default_bounds` clarifies that cached “default filter” bounds are really cached bounds for a specific reduced subtree filter.
---
## Key function: `CAniMesh::AppendMshResourcePieces (AniMesh.dll/sub_1000ac70)`
```
typedef struct GmsgAppendResourcePayload_CAniMesh {
char archive_name[32];
char msh_archive_entry_name[32];
uint msh_tag;
uint attach_parent_absolute_piece_index;
uint material_id_hi;
} GmsgAppendResourcePayload_CAniMesh;
```
Core behavior:
```text
One call loads one .msh resource.
All pieces created by that call receive the same tag.
The created pieces are appended to CAniMesh::pieces_vector.
```
For `tag == 0`:
```text
source MSH 0x01 node 0 -> piece[0]
source MSH 0x01 node 1 -> piece[1]
source MSH 0x01 node 2 -> piece[2]
...
```
For `tag != 0`:
```text
source MSH 0x01 node 0 is skipped
source MSH 0x01 node 1 -> first newly created piece
source MSH 0x01 node 2 -> next newly created piece
...
```
Attached parent remap rule:
```text
source parent == 0
-> attach_parent_absolute_piece_index
source parent > 0
-> first_new_piece_index + (source_parent - 1)
source parent == 0xFFFF
-> -1 / no parent
```
This means internal parent hierarchy inside the attached `.msh` is preserved, but all indices are converted to absolute `pieces_vector` indices.
---
Runtime piece flag:
```c
#define MSH_PIECE_FLAG_SUBMODEL_ROOT 0x01000000
```
Meaning:
```text
Set on the first runtime piece created by one .msh load call.
For tag == 0, this is the main model root.
For tag != 0, this is the attached submodel/subtree root.
```
### This function creates a runtime representation of a .msh 0x01 piece
```
typedef struct MSH_piece {
uint msh_tag_0x00;
int local_parent_index_base;
uint msh0x01_node_index;
undefined4 field_12;
undefined4 material_id;
EMshPieceFlags flags;
int parent_piece_index;
EMeshPieceState state;
Matrix4x4 world_pose_matrix;
Matrix4x4 mesh_space_pose_matrix;
Matrix4x4 local_pose_matrix;
Quaternion orientation_blend_start_quat;
Quaternion orientation_blend_end_quat;
float anim1_time_start;
float anim1_time_target;
float anim2_time_start;
float anim2_time_target;
bool is_pose_cache_valid;
bool exclude_from_pose_update_order;
bool uses_local_anim_blend;
undefined1 has_orientation_blend;
float local_anim_transition_progress;
float local_anim_blend_factor;
float cached_anim1_sample_time;
float cached_anim2_sample_time;
float render_phase_0x124??;
undefined4 material_phase_0x128??;
MSH_Reader * msh_reader;
} MSH_piece;
```
---
## Key type: `MSH_0x01_node`
`MSH` component `0x01` is a source node / piece table.
Important fields:
```c
typedef struct MSH_0x01_node {
uint16_t flags;
uint16_t parent_index_or_link;
uint16_t anim_map_start_0x13;
uint16_t fallback_key_0x08;
uint16_t msh02_slot_indices_by_state_and_lod[3][5];
} MSH_0x01_node;
```
Meaning:
```text
MSH 0x01 node
-> becomes an MSH_piece at runtime
-> has parent_index_or_link
-> has MSH01 flags
-> maps LOD/state to MSH 0x02 geometry slots
```
---
## Key type: `MSH_02_geometry_slot`
`MSH` component `0x02` stores geometry slot metadata and local bounds.
Preferred structure:
```c
typedef struct MSH_02_geometry_slot {
uint16_t tri_start_0x07;
uint16_t tri_count_0x07;
uint16_t batch_start_0x0d;
uint16_t batch_count_0x0d;
Vector3 local_minimum;
Vector3 local_maximum;
Sphere bounding_sphere;
float base_xy_area;
float base_volume;
uint32_t opaque_0x38;
uint32_t opaque_0x3C;
uint32_t opaque_0x40;
} MSH_02_geometry_slot;
```
## Key function: `ResolveMsh0x02SlotBy_LOD_and_state`
The bounds functions call it as default geometry lookup:
```c
MSH_02_geometry_slot *
ResolveMsh0x02SlotBy_LOD_and_state(
MSH_piece *this,
EMeshPieceLodLevel lod_level,
EMeshPieceState state
);
typedef enum EMeshPieceLodLevel {
LOD_LEVEL_MAX_0 = 0,
LOD_LEVEL_MINUS_1 = 1,
LOD_LEVEL_MINUS_2 = 2,
LOD_LEVEL_MINUS_3 = 3,
LOD_LEVEL_MINUS_4 = 4,
} EMeshPieceLodLevel;
typedef enum EMeshPieceState {
MODEL_STATE_DEFAULT = -1,
MODEL_STATE_REGULAR = 0,
MODEL_STATE_COLLAPSED = 1,
_MODEL_STATE_UNKNOWN_2 = 2,
} EMeshPieceState;
```
---
## Key function: `IJointMesh_of_AniMesh::ComputeJointBoundingBox`
Preferred name:
```c
AniMesh_IJointMesh::ComputeJointBoundingBox
```
Core behavior:
```text
1. Read the joint/piece placement matrix in requested space.
2. Resolve default geometry slot for the queried piece.
3. If the piece has geometry:
- build local box from slot local_minimum/local_maximum;
- optionally scale by mesh_scale;
- transform all corners by the joint matrix.
4. If the piece has no geometry:
- create a degenerate box at joint transform origin.
5. If scope is single-piece, return.
6. If queried piece is root piece 0, return cached whole-mesh bounds.
7. Otherwise recursively include matching children, controlled by JointBoundsFilter and MSH01 flags.
```
Important meaning:
```text
This is the main recursive piece-tree bounds function.
```
Geometry-less piece meaning:
```text
No MSH 0x02 slot
-> helper/socket joint
-> point-sized bounds at joint transform origin
```
---
## Key function: `IJointMesh_of_AniMesh::ComputeJointBoundingSphere`
Preferred name:
```c
AniMesh_IJointMesh::ComputeJointBoundingSphere
```
Core behavior:
```text
If scope != SINGLE_PIECE and piece != 0:
ComputeJointBoundingBox(...)
Convert resulting AABB to center/radius sphere.
If scope != SINGLE_PIECE and piece == 0:
Use cached whole-mesh or cached filtered mesh sphere.
If scope == SINGLE_PIECE:
Use MSH_02_geometry_slot::bounding_sphere.
If no geometry slot, return zero-radius sphere at joint origin.
```
Important meaning:
```text
Subtree sphere is not a tight recursive sphere.
It is an AABB-derived sphere from ComputeJointBoundingBox.
```
---
## Important conceptual model
```text
CAniMesh
owns one flat pieces_vector
Each loaded .msh
contributes one tagged group of pieces
MSH 0x01
source node hierarchy inside one .msh
MSH_piece
runtime node/piece inside CAniMesh::pieces_vector
parent_piece_index
absolute runtime parent index in CAniMesh::pieces_vector
msh_tag
tells which loaded .msh/submodel this runtime piece came from
```
Runtime result:
```text
Multiple .msh files become one combined piece tree.
The tag preserves source submodel grouping.
The parent indices define the actual runtime hierarchy.
```
---
## Example runtime structure
```text
CAniMesh pieces_vector
piece[0] body_root tag = 0, SUBMODEL_ROOT
├─ piece[1] left_track tag = 0
├─ piece[2] right_track tag = 0
└─ piece[3] turret_socket tag = 0
└─ piece[4] turret_base tag = 1, SUBMODEL_ROOT
└─ piece[5] turret_rotor tag = 1
├─ piece[6] cannon_socket tag = 1
│ └─ piece[8] cannon_body tag = 2, SUBMODEL_ROOT
└─ piece[7] rocket_socket tag = 1
└─ piece[9] launcher_body tag = 3, SUBMODEL_ROOT
```
Key rule:
```text
tag groups pieces by loaded .msh.
parent_piece_index builds the actual hierarchy.
```