Skip to content

Added pmxRigidBodyConstraint command - #9

Merged
sstarosz merged 4 commits into
mainfrom
feature/rigid-body-constraint-cmd
Aug 10, 2026
Merged

Added pmxRigidBodyConstraint command#9
sstarosz merged 4 commits into
mainfrom
feature/rigid-body-constraint-cmd

Conversation

@sstarosz

@sstarosz sstarosz commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Adds the pmxRigidBodyConstraint command that populates the pmxPhysicsNode's joints array with PMX rigid-body constraint data at import time.

What

  • pmxRigidBodyConstraint command (create mode only) — native C++ (rigid_body_constraint_cmd.hpp/.cpp), pure-interface header + anonymous-namespace helpers, same design as pmxRigidBody (Added pmxRigidBody command #8).
    • Appends ONE PMX joint to joints[] at the next free index; returns the new index.
    • Writes jointBodyA/jointBodyB, jointType, joint frame, linear/angular limits and springs.
    • Conversions match the old Python writer exactly: frame translate Z-flip, frame rotate MMD radians → Maya degrees with the handedness flip, limits/springs verbatim (angular stays in PMX radians — the node hands them to Bullet unchanged).
    • Validated: joint type 0..5; bodyA/bodyB in range against the current body count; -index must be the next free index. Edit/query rejected (not implemented yet).
  • Registered in MayaMMD.cpp + CMakeLists.txt.
  • Import wiringrigid_body_builder.py populates joints[] through the command after every body exists (constraints reference bodies by index).
  • Tests — 11 new command integration tests (mock scene, no PMX) + import-suite assertions that the joints array mirrors the PMX (count + first-joint data). cmd suite: 47 passed (36 body + 11 constraint); import suite passes with 319 bodies / 496 joints on Miku.
  • CHANGELOG entry.

…ata)

- rigid_body_constraint_cmd.hpp/.cpp: native C++ pmxRigidBodyConstraint
  command (create mode only — the default).  Appends ONE PMX joint to an
  pmxPhysicsNode's 'joints' array at the next free index, replacing the
  former Python _set_joint_attributes.  Pure-interface header; all helpers
  in an anonymous namespace (same design as pmxRigidBody).
  Conversions match the old Python writer exactly: frame translate Z-flip,
  frame rotate MMD radians -> Maya degrees with the handedness flip, and
  limits/springs VERBATIM (angular stays in PMX radians — the node hands
  angular values to Bullet unchanged).  Validates: joint type 0..5,
  bodyA/bodyB against the current body count, -index == next free index.
- MayaMMD.cpp: register/deregister the command.
- rigid_body_builder.py: populate the 'joints' array through the command
  after every body exists (constraints reference bodies by index).
- Import suite: assert joints count + first-joint data mirror the PMX.
- 11 new command integration tests (mock scene, no PMX); cmd suite 47
  passed, import suite passes with 319 bodies / 496 joints on Miku.
- CHANGELOG entry.
…nt names, self-constraint guard

Harsh-review findings (PR #9) and fixes:

1. P1 BUG — asymmetric limits were stored MIRRORED.  The MMD->Maya
   conversion is the reflection F = diag(1,1,-1) (Z-flip + rotation
   negation on X/Y), and the joint limits live in the FRAME's local space,
   so they must transform under the same reflection.  The old writer (and
   this PR's first cut) passed them through verbatim: a rotation about
   local X/Y negates under conjugation, so the [min,max] interval negates
   AND swaps; linear Z negates (local Z axis reverses) so its interval
   negates AND swaps; angular Z and springs are invariant.  On the test
   model 429/496 joints have asymmetric angular limits (e.g. chest min
   -0.524..max +0.087) — without the fix they allow rotation in the wrong
   sense.  Confirmed against blender_mmd_tools (which applies the mapping
   to location limits and negates+swaps rotation limits).
   Fix: linearMin' = (x, y, -max.z), linearMax' = (x, y, -min.z);
   angularMin' = (-max.x, -max.y, min.z), angularMax' = (-min.x, -min.y,
   max.z); springs verbatim.

2. P2 CONSISTENCY — the joint frame was stored in world space while the
   bodies store group-space rest poses (pmxRigidBody converts).  With an
   identity physics group (import) they coincide; a transformed group
   would detach the frames from the bodies.  Fix: store the frame in group
   space (world * groupWorld^-1), mirroring pmxRigidBody.

3. P3 DATA FIDELITY — PMX joint names were dropped (the node had no
   joint-name attributes; bodies store theirs).  Fix: add
   jointNameLocal/jointNameUniversal to the node (additive, not hashed in
   the config signature like the body names) + -name/-nameUniversal flags
   + the builder passes them.

4. P4 HARDENING — bodyA == bodyB (a body constraining itself) is
   degenerate; PMX never produces it (0/496).  Fix: reject it.

Coverage added: asymmetric limit reflection (real-model chest values),
linear-Z swap, group-space frame with a transformed group, self-constraint
rejection, joint names; the import suite now asserts the first joint's
name/frame/angular-limit reflection against real PMX data.  cmd suite 50
passed (36 body + 14 constraint), import 8, node 17.
Second review pass on PR #9:

- Refactor: matrixFromTR (translate + XYZ euler degrees -> MMatrix) was
  duplicated in the rigid_body_cmd and rigid_body_constraint_cmd anonymous
  namespaces.  Consolidated into mmd::maya::matrixFromTR in maya_utils.hpp;
  both commands now use the shared helper (unused deg2rad using-decls
  removed).

- Hardening: a PMX body that fails to create during import (the builder
  logs and continues) silently shifts every later PMX body index, so the
  constraints (and future write-back) would reference WRONG bodies.
  _populate_rigid_bodies now returns the number created, and
  create_physics_from_pmx_data logs a prominent warning when it does not
  match len(pmx_data.rigid_bodies).

Verified: all 12,548 joints across every asset model have valid, distinct
body references (0 self-joints, 0 out-of-range) — the bodyA!=bodyB and
range validations are import-safe for the whole database.  cmd 50 passed,
import + node suites pass, clang-tidy clean.
Convert the pmxPhysicsNode joints[] jointType child from a plain long to
an MFnEnumAttribute, matching the bodyGroupId/bodyColliderType/
bodyPhysicsMode pattern: one field per PMX JointType (Spring6Dof/SixDof/
P2P/ConeTwist/Slider/Hinge, values 0..5), so the Attribute Editor shows a
dropdown instead of a raw integer.  The command writes it via setShort
(like bodyGroupId); readJointData / config signature read it via asInt,
unchanged.  Storable/keyable set in the enum block (kept out of the
numeric loop).

- jointType is an enum dropdown in the Attribute Editor.
- New cmd test: attributeQuery(listEnum) exposes the six fields; writing
  type=4 (Slider) round-trips as 4.
- cmd suite 51 passed (36 body + 15 constraint), import + node pass.
@sstarosz sstarosz added the Physics Physics system label Aug 10, 2026
@sstarosz sstarosz changed the title Cmd: add native pmxRigidBodyConstraint command (create mode, joints data) Added pmxRigidBodyConstraint command Aug 10, 2026
@sstarosz
sstarosz merged commit 29ac2ea into main Aug 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Physics Physics system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant