From 7e396ce9f765062db30bd21d18a583528a02958a Mon Sep 17 00:00:00 2001 From: YIN-Tairan Date: Fri, 13 Feb 2026 13:08:05 +0100 Subject: [PATCH 1/7] Modify some private function to protected virtual to enable override in a child class --- com.jlpm.motionmatching/Runtime/Unity/Obstacle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.jlpm.motionmatching/Runtime/Unity/Obstacle.cs b/com.jlpm.motionmatching/Runtime/Unity/Obstacle.cs index bea9bdbe..691e5046 100644 --- a/com.jlpm.motionmatching/Runtime/Unity/Obstacle.cs +++ b/com.jlpm.motionmatching/Runtime/Unity/Obstacle.cs @@ -36,7 +36,7 @@ private void OnDisable() ObstacleManager.Instance.UnregisterObstacle(this); } - public bool GetCurrentSteering(out float2 steering) + public virtual bool GetCurrentSteering(out float2 steering) { if (CrowdSplineCharacterController != null) { @@ -52,7 +52,7 @@ public bool GetCurrentSteering(out float2 steering) return false; } - public (float3, bool, float4) GetProjWorldPosition(int trajectoryIndex, bool forceCurrent = false) + public virtual (float3, bool, float4) GetProjWorldPosition(int trajectoryIndex, bool forceCurrent = false) { if (IsStatic || forceCurrent) { @@ -117,7 +117,7 @@ public bool Intersect(float2 rayOrigin, float2 rayDirection, out float2 hitPoint return true; } - private void OnDrawGizmos() + protected virtual void OnDrawGizmos() { Gizmos.color = Color.red; Vector3 position = transform.position; From 4c5e5fae450e2f226e6fb13aaf5c7c858e803044 Mon Sep 17 00:00:00 2001 From: YIN-Tairan Date: Fri, 13 Feb 2026 15:11:46 +0100 Subject: [PATCH 2/7] bring back the hacky codes after manual upgrade --- com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs | 8 ++++++++ com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs b/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs index b187de1d..389ff8ca 100644 --- a/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs +++ b/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs @@ -24,6 +24,14 @@ public BVHAnimation() EndSites = new List(); } + public BVHAnimation(Skeleton skeleton, List endSites, Frame[] frames, float frameDuration) + { + Skeleton = skeleton; + EndSites = endSites; + Frames = frames; + FrameTime = frameDuration; + } + public void SetFrameTime(float frameTime) { FrameTime = frameTime; diff --git a/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs b/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs index 1a0a6c5b..893e7015 100644 --- a/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs +++ b/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs @@ -23,6 +23,8 @@ public class AnimationData : ScriptableObject private BVHAnimation Animation; + public BVHAnimation HackAnimation { get { return Animation; } set { Animation = value; } } + public void Import() { BVHImporter importer = new BVHImporter(); From 08a6640e77c8c90acce9cb2a0d0e9a7e1ef4fbaf Mon Sep 17 00:00:00 2001 From: JLPM22 Date: Tue, 24 Feb 2026 12:21:11 +0100 Subject: [PATCH 3/7] Refactor: Add SetAnimation and maintain GetAnimation (replacing HackAnimation) --- com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs | 4 ++-- com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs b/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs index 389ff8ca..6cc54fbf 100644 --- a/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs +++ b/com.jlpm.motionmatching/Runtime/BVH/BVHAnimation.cs @@ -24,12 +24,12 @@ public BVHAnimation() EndSites = new List(); } - public BVHAnimation(Skeleton skeleton, List endSites, Frame[] frames, float frameDuration) + public BVHAnimation(Skeleton skeleton, List endSites, Frame[] frames, float frameTime) { Skeleton = skeleton; EndSites = endSites; Frames = frames; - FrameTime = frameDuration; + FrameTime = frameTime; } public void SetFrameTime(float frameTime) diff --git a/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs b/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs index 893e7015..4db4b93c 100644 --- a/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs +++ b/com.jlpm.motionmatching/Runtime/Unity/AnimationData.cs @@ -23,8 +23,6 @@ public class AnimationData : ScriptableObject private BVHAnimation Animation; - public BVHAnimation HackAnimation { get { return Animation; } set { Animation = value; } } - public void Import() { BVHImporter importer = new BVHImporter(); @@ -40,6 +38,11 @@ public BVHAnimation GetAnimation() return Animation; } + public void SetAnimation(BVHAnimation animation) + { + Animation = animation; + } + public List GetTags() { return Tags; From 65e6fd6f532b4e1bad5faef29df8e5dbe88142f9 Mon Sep 17 00:00:00 2001 From: JLPM22 Date: Tue, 24 Feb 2026 13:08:34 +0100 Subject: [PATCH 4/7] Optimize PoseSet deserialization and memory management. Closes #8 --- .../Runtime/Pose/PoseSerializer.cs | 187 ++++++++++++------ .../Runtime/Pose/PoseSet.cs | 12 ++ 2 files changed, 135 insertions(+), 64 deletions(-) diff --git a/com.jlpm.motionmatching/Runtime/Pose/PoseSerializer.cs b/com.jlpm.motionmatching/Runtime/Pose/PoseSerializer.cs index 017c781b..5e63a047 100644 --- a/com.jlpm.motionmatching/Runtime/Pose/PoseSerializer.cs +++ b/com.jlpm.motionmatching/Runtime/Pose/PoseSerializer.cs @@ -5,6 +5,7 @@ using System.IO; using System.Text; using System; +using System.Runtime.InteropServices; namespace MotionMatching { @@ -96,92 +97,150 @@ public bool Deserialize(string path, string fileName, MotionMatchingData mmData, { poseSet = new PoseSet(mmData); - // Read Skeleton - Skeleton skeleton = new Skeleton(); + // -------------------- + // Read Skeleton File + // -------------------- string skeletonPath = Path.Combine(path, fileName + ".mmskeleton"); - if (File.Exists(skeletonPath)) + if (!File.Exists(skeletonPath)) + return false; + + Skeleton skeleton = new Skeleton(); + byte[] skeletonData = File.ReadAllBytes(skeletonPath); + using (var ms = new MemoryStream(skeletonData)) { - using (var stream = File.Open(skeletonPath, FileMode.Open)) + using (var reader = new BinaryReader(ms, Encoding.UTF8)) { - using (var reader = new BinaryReader(stream, System.Text.Encoding.UTF8)) + uint nJoints = reader.ReadUInt32(); + skeleton.Joints.Capacity = (int)nJoints; + for (int i = 0; i < nJoints; i++) { - // Read Number Joints - uint nJoints = reader.ReadUInt32(); - // Read Joints - for (int i = 0; i < nJoints; i++) - { - string jointName = reader.ReadString(); - uint jointIndex = reader.ReadUInt32(); - uint jointParentIndex = reader.ReadUInt32(); - float3 jointLocalOffset = ReadFloat3(reader); - HumanBodyBones jointType = (HumanBodyBones)reader.ReadUInt32(); - skeleton.AddJoint(new Skeleton.Joint(jointName, (int)jointIndex, (int)jointParentIndex, jointLocalOffset, jointType)); - } + string jointName = reader.ReadString(); + uint jointIndex = reader.ReadUInt32(); + uint jointParentIndex = reader.ReadUInt32(); + float3 jointLocalOffset = ReadFloat3(reader); + HumanBodyBones jointType = (HumanBodyBones)reader.ReadUInt32(); + skeleton.AddJoint(new Skeleton.Joint(jointName, (int)jointIndex, (int)jointParentIndex, jointLocalOffset, jointType)); } } } - else return false; - // Set skeleton in poseSet poseSet.SetSkeletonFromFile(skeleton); - // Read Poses + // -------------------- + // Read Pose File + // -------------------- string posePath = Path.Combine(path, fileName + ".mmpose"); - if (File.Exists(posePath)) + if (!File.Exists(posePath)) + return false; + + byte[] poseData = File.ReadAllBytes(posePath); + using (var ms = new MemoryStream(poseData)) { - using (var stream = File.Open(posePath, FileMode.Open)) + using (var reader = new BinaryReader(ms, Encoding.UTF8)) { - using (var reader = new BinaryReader(stream, System.Text.Encoding.UTF8)) + uint nClips = reader.ReadUInt32(); + poseSet.SetClipCapacity(nClips); + for (int i = 0; i < nClips; i++) { - // Deserialize Number Animation Clips - uint nClips = reader.ReadUInt32(); - // Deserialize Animation Clips - for (int i = 0; i < nClips; i++) + uint start = reader.ReadUInt32(); + uint end = reader.ReadUInt32(); + float frameTime = reader.ReadSingle(); + poseSet.AddAnimationClipDeserialized(new PoseSet.AnimationClip((int)start, (int)end, frameTime)); + } + + uint nPoses = reader.ReadUInt32(); + uint nJoints = reader.ReadUInt32(); + uint nTags = reader.ReadUInt32(); + Debug.Assert(nJoints == skeleton.Joints.Count, "Number of joints in skeleton and pose do not match"); + + // Precompute sizes for the buffers (they remain constant across iterations) + int float3BufferSize = (int)nJoints * 3 * sizeof(float); + int quaternionBufferSize = (int)nJoints * 4 * sizeof(float); + + // Allocate reusable buffers once outside the loop + byte[] float3Buffer = new byte[float3BufferSize]; + byte[] quaternionBuffer = new byte[quaternionBufferSize]; + + poseSet.SetPoseCapacity(nPoses); + for (int i = 0; i < nPoses; i++) + { + PoseVector pose = new PoseVector(); + + // --- Read JointLocalPositions --- + reader.Read(float3Buffer, 0, float3BufferSize); + Span positionsSpan = MemoryMarshal.Cast(float3Buffer); + pose.JointLocalPositions = new float3[nJoints]; + for (int j = 0; j < nJoints; j++) { - uint start = reader.ReadUInt32(); - uint end = reader.ReadUInt32(); - float frameTime = reader.ReadSingle(); - poseSet.AddAnimationClipDeserialized(new PoseSet.AnimationClip((int)start, (int)end, frameTime)); + pose.JointLocalPositions[j] = new float3( + positionsSpan[j * 3], + positionsSpan[j * 3 + 1], + positionsSpan[j * 3 + 2] + ); } - // Deserialize Number Poses & Number Joints & Number Tags - uint nPoses = reader.ReadUInt32(); - uint nJoints = reader.ReadUInt32(); - uint nTags = reader.ReadUInt32(); - Debug.Assert(nJoints == skeleton.Joints.Count, "Number of joints in skeleton and pose do not match"); - // Deserialize Poses - PoseVector[] poses = new PoseVector[nPoses]; - for (int i = 0; i < nPoses; i++) + + // --- Read JointLocalRotations --- + reader.Read(quaternionBuffer, 0, quaternionBufferSize); + Span rotationsSpan = MemoryMarshal.Cast(quaternionBuffer); + pose.JointLocalRotations = new quaternion[nJoints]; + for (int j = 0; j < nJoints; j++) { - PoseVector pose = new PoseVector(); - pose.JointLocalPositions = ReadFloat3Array(reader, nJoints); - pose.JointLocalRotations = ReadQuaternionArray(reader, nJoints); - pose.JointLocalVelocities = ReadFloat3Array(reader, nJoints); - pose.JointLocalAngularVelocities = ReadFloat3Array(reader, nJoints); - pose.LeftFootContact = reader.ReadUInt32() == 1u; - pose.RightFootContact = reader.ReadUInt32() == 1u; - poses[i] = pose; + pose.JointLocalRotations[j] = new quaternion( + rotationsSpan[j * 4], + rotationsSpan[j * 4 + 1], + rotationsSpan[j * 4 + 2], + rotationsSpan[j * 4 + 3] + ); } - // Set Poses in poseSet - poseSet.AddClipDeserialized(poses); - // Deserialize Tags - for (int i = 0; i < nTags; ++i) + + // --- Read JointLocalVelocities --- + reader.Read(float3Buffer, 0, float3BufferSize); + Span velocitiesSpan = MemoryMarshal.Cast(float3Buffer); + pose.JointLocalVelocities = new float3[nJoints]; + for (int j = 0; j < nJoints; j++) { - string name = reader.ReadString(); - int nRanges = (int)reader.ReadUInt32(); - List tagStarts = new List(nRanges); - List tagEnds = new List(nRanges); - for (int r = 0; r < nRanges; ++r) - { - tagStarts.Add((int)reader.ReadUInt32()); - tagEnds.Add((int)reader.ReadUInt32()); - } - poseSet.AddTagDeserialized(name, tagStarts, tagEnds); + pose.JointLocalVelocities[j] = new float3( + velocitiesSpan[j * 3], + velocitiesSpan[j * 3 + 1], + velocitiesSpan[j * 3 + 2] + ); } - poseSet.ConvertTagsToNativeArrays(); + + // --- Read JointLocalAngularVelocities --- + reader.Read(float3Buffer, 0, float3BufferSize); + Span angularVelocitiesSpan = MemoryMarshal.Cast(float3Buffer); + pose.JointLocalAngularVelocities = new float3[nJoints]; + for (int j = 0; j < nJoints; j++) + { + pose.JointLocalAngularVelocities[j] = new float3( + angularVelocitiesSpan[j * 3], + angularVelocitiesSpan[j * 3 + 1], + angularVelocitiesSpan[j * 3 + 2] + ); + } + + // --- Read contact flags --- + pose.LeftFootContact = reader.ReadUInt32() == 1u; + pose.RightFootContact = reader.ReadUInt32() == 1u; + + poseSet.AddClip(pose); } + + for (int i = 0; i < nTags; i++) + { + string name = reader.ReadString(); + int nRanges = (int)reader.ReadUInt32(); + List tagStarts = new List(nRanges); + List tagEnds = new List(nRanges); + for (int r = 0; r < nRanges; r++) + { + tagStarts.Add((int)reader.ReadUInt32()); + tagEnds.Add((int)reader.ReadUInt32()); + } + poseSet.AddTagDeserialized(name, tagStarts, tagEnds); + } + poseSet.ConvertTagsToNativeArrays(); } } - else return false; - return true; } } diff --git a/com.jlpm.motionmatching/Runtime/Pose/PoseSet.cs b/com.jlpm.motionmatching/Runtime/Pose/PoseSet.cs index 7c0e331d..f481dd36 100644 --- a/com.jlpm.motionmatching/Runtime/Pose/PoseSet.cs +++ b/com.jlpm.motionmatching/Runtime/Pose/PoseSet.cs @@ -102,6 +102,18 @@ public bool AddClip(PoseVector[] poses, float frameTime, out int animationClip) return true; } + public void AddClip(PoseVector pose) + { + Poses.Add(pose); + } + public void SetPoseCapacity(uint numPoses) + { + Poses.Capacity = (int)numPoses; + } + public void SetClipCapacity(uint count) + { + Clips.Capacity = (int)count; + } /// /// Add a tag to the current pose set From 47f3386d78fe451ddbd82782b64ee8d97a2d7d85 Mon Sep 17 00:00:00 2001 From: JLPM22 Date: Tue, 24 Feb 2026 13:12:58 +0100 Subject: [PATCH 5/7] Fix NativeArray construction bug when tag is not found. Closes #11 --- com.jlpm.motionmatching/Runtime/Core/Tags.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.jlpm.motionmatching/Runtime/Core/Tags.cs b/com.jlpm.motionmatching/Runtime/Core/Tags.cs index 217a9b22..4f69aac0 100644 --- a/com.jlpm.motionmatching/Runtime/Core/Tags.cs +++ b/com.jlpm.motionmatching/Runtime/Core/Tags.cs @@ -80,8 +80,8 @@ bool GetRangesFromGraphIndex(int g, out NativeArray start, out NativeArray< return true; } } - start = new NativeArray(); - end = new NativeArray(); + start = new NativeArray(0, Allocator.Temp); + end = new NativeArray(0, Allocator.Temp); return false; } else From 01286b463ca8c85f2d0cb907a7b942afff1b509e Mon Sep 17 00:00:00 2001 From: JLPM22 Date: Tue, 24 Feb 2026 13:21:48 +0100 Subject: [PATCH 6/7] new version 0.3.2 --- com.jlpm.motionmatching/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.jlpm.motionmatching/package.json b/com.jlpm.motionmatching/package.json index 28898ed4..81e3adfd 100644 --- a/com.jlpm.motionmatching/package.json +++ b/com.jlpm.motionmatching/package.json @@ -1,6 +1,6 @@ { "name": "com.jlpm.motionmatching", - "version": "0.3.1", + "version": "0.3.2", "displayName": "Motion Matching", "description": "Motion Matching implementation for Unity", "unity": "2020.1", From 35fc8ee9a0fd97f12de458ea81eaef7c8219d87a Mon Sep 17 00:00:00 2001 From: Tairan Yin <104104297+YIN-Tairan@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:55:44 +0200 Subject: [PATCH 7/7] Fix null exception when FeatureWeights is null (#15) --- .../Runtime/Core/MotionMatchingController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.jlpm.motionmatching/Runtime/Core/MotionMatchingController.cs b/com.jlpm.motionmatching/Runtime/Core/MotionMatchingController.cs index ee79c317..47f07438 100644 --- a/com.jlpm.motionmatching/Runtime/Core/MotionMatchingController.cs +++ b/com.jlpm.motionmatching/Runtime/Core/MotionMatchingController.cs @@ -117,7 +117,7 @@ private void Awake() { float[] newWeights = new float[numberFeatures]; for (int i = 0; i < newWeights.Length; ++i) newWeights[i] = 1.0f; - for (int i = 0; i < Mathf.Min(FeatureWeights.Length, newWeights.Length); i++) newWeights[i] = FeatureWeights[i]; + for (int i = 0; i < Mathf.Min(FeatureWeights?.Length ?? 0, newWeights.Length); i++) newWeights[i] = FeatureWeights[i]; FeatureWeights = newWeights; } FeaturesWeightsNativeArray = new NativeArray(FeatureSet.FeatureSize, Allocator.Persistent);