| 1783 | // ============================================================================= |
| 1784 | |
| 1785 | TSharedPtr<FJsonObject> FURLabRpcDispatcher::HandleReset(const TSharedPtr<FJsonObject>& Req) |
| 1786 | { |
| 1787 | AAMjManager* Mgr = OwnerMgr.Get(); |
| 1788 | if (!Mgr || !Mgr->PhysicsEngine || !Mgr->PhysicsEngine->m_model) |
| 1789 | { |
| 1790 | return MakeError(TEXT("not_ready"), TEXT("PhysicsEngine not initialised")); |
| 1791 | } |
| 1792 | |
| 1793 | mjModel* m = Mgr->PhysicsEngine->GetModel(); |
| 1794 | mjData* d = Mgr->PhysicsEngine->GetData(); |
| 1795 | |
| 1796 | int32 SeedVal = 0; |
| 1797 | if (Req->TryGetNumberField(TEXT("seed"), SeedVal)) |
| 1798 | { |
| 1799 | Mgr->Seed = SeedVal; |
| 1800 | // Modern mjOption has no "seed" field; mj_step is deterministic and |
| 1801 | // doesn't depend on a stored seed (random elements come from |
| 1802 | // user-set noise inputs, not an integrator-internal RNG). The seed |
| 1803 | // is recorded on the manager so any RNG used by client code or by |
| 1804 | // the recording layer can mirror it for reproducibility. UE itself |
| 1805 | // does not reseed the integrator here. |
| 1806 | } |
| 1807 | |
| 1808 | { |
| 1809 | FScopeLock Lock(&Mgr->PhysicsEngine->CallbackMutex); |
| 1810 | |
| 1811 | FString KfName; |
| 1812 | if (Req->TryGetStringField(TEXT("keyframe_name"), KfName) && !KfName.IsEmpty()) |
| 1813 | { |
| 1814 | int Kid = mj_name2id(m, mjOBJ_KEY, TCHAR_TO_UTF8(*KfName)); |
| 1815 | if (Kid < 0) |
| 1816 | return MakeError(TEXT("unknown_keyframe"), KfName); |
| 1817 | mj_resetDataKeyframe(m, d, Kid); |
| 1818 | } |
| 1819 | else |
| 1820 | { |
| 1821 | mj_resetData(m, d); |
| 1822 | } |
| 1823 | |
| 1824 | // Per-articulation qpos overrides (joint-name -> value). |
| 1825 | const TSharedPtr<FJsonObject>* PerArt = nullptr; |
| 1826 | if (Req->TryGetObjectField(TEXT("per_articulation_qpos"), PerArt) && PerArt && PerArt->IsValid()) |
| 1827 | { |
| 1828 | for (auto& APair : (*PerArt)->Values) |
| 1829 | { |
| 1830 | AMjArticulation* Art = Mgr->GetArticulation(APair.Key); |
| 1831 | if (!Art) |
| 1832 | continue; |
| 1833 | const TSharedPtr<FJsonObject>* QObj = nullptr; |
| 1834 | if (!APair.Value->TryGetObject(QObj) || !QObj || !QObj->IsValid()) |
| 1835 | continue; |
| 1836 | |
| 1837 | FString Prefix = Art->GetName() + TEXT("_"); |
| 1838 | for (auto& JPair : (*QObj)->Values) |
| 1839 | { |
| 1840 | FString FullName = Prefix + JPair.Key; |
| 1841 | int Jid = mj_name2id(m, mjOBJ_JOINT, TCHAR_TO_UTF8(*FullName)); |
| 1842 | if (Jid < 0) |