From 199c72b1920ed9d1e96148c15daa209a0e19d67e Mon Sep 17 00:00:00 2001 From: towneh <25694892+towneh@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:21:12 +0100 Subject: [PATCH 1/2] feat: add Sequence speed multiplier to BasisAuthoredMotion Adds a sequenceSpeed field to the Sequence movement kind so a baked clip can be sped up or slowed down. The Burst job scales the playhead by the multiplier before the loop/clamp, so it applies to both looping and one-shot clips; negative values play in reverse. Defaults to 1 (authored speed), so existing components are unchanged. --- .../Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs | 5 ++++- Basis/Packages/com.basis.sdk/Localization/Languages/en.json | 2 ++ Basis/Packages/com.basis.sdk/Scripts/BasisAuthoredMotion.cs | 1 + .../Scripts/Editor/BasisAuthoredMotionEditor.cs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs b/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs index f7776d322..b6c14953e 100644 --- a/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs +++ b/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs @@ -47,6 +47,7 @@ public struct AuthoredMovementData public int frameCount; // frames for this slot's transform public float frameRate; // samples per second public int loop; // 0 = one-shot, 1 = loop + public float sequenceSpeed; // playback rate multiplier (negative reverses) // Captured rest pose (local space); motion composes as a delta from this. public quaternion restRotation; @@ -145,7 +146,8 @@ public void Execute(int index, TransformAccess transform) if (m.frameCount <= 0) break; float fr = m.frameRate > 1e-4f ? m.frameRate : 30f; float length = m.frameCount / fr; - float pt = m.loop != 0 ? math.fmod(t, length) : math.min(math.max(t, 0f), length); + float st = t * m.sequenceSpeed; + float pt = m.loop != 0 ? math.fmod(st, length) : math.min(math.max(st, 0f), length); if (pt < 0f) pt += length; // fmod can return negative for negative t float frameF = pt * fr; @@ -501,6 +503,7 @@ static Registration Build(BasisAuthoredMotion component) d.frameCount = fc; d.frameRate = clip.frameRate; d.loop = mv.loop ? 1 : 0; + d.sequenceSpeed = mv.sequenceSpeed; AddSlot(data, targets, movementEnabled, d, tf, mv.enabled); } break; diff --git a/Basis/Packages/com.basis.sdk/Localization/Languages/en.json b/Basis/Packages/com.basis.sdk/Localization/Languages/en.json index 95aec86c7..ef2c701ba 100644 --- a/Basis/Packages/com.basis.sdk/Localization/Languages/en.json +++ b/Basis/Packages/com.basis.sdk/Localization/Languages/en.json @@ -261,6 +261,8 @@ { "key": "sdk.authoredMotion.field.keyframes.tooltip", "value": "Inline pose-delta timeline for short motion. Ignored when a Baked Clip is assigned." }, { "key": "sdk.authoredMotion.field.loop.label", "value": "Loop" }, { "key": "sdk.authoredMotion.field.loop.tooltip", "value": "Loop the sequence, or play it once." }, + { "key": "sdk.authoredMotion.field.sequenceSpeed.label", "value": "Speed" }, + { "key": "sdk.authoredMotion.field.sequenceSpeed.tooltip", "value": "Playback rate multiplier for the baked clip. 1 plays at authored speed, 2 plays twice as fast, 0.5 half speed; negative values play in reverse." }, { "key": "sdk.buildReport.window.title", "value": "Basis Build Report Viewer" }, { "key": "sdk.buildReport.window.tabTitle", "value": "Basis Bundle Report" }, diff --git a/Basis/Packages/com.basis.sdk/Scripts/BasisAuthoredMotion.cs b/Basis/Packages/com.basis.sdk/Scripts/BasisAuthoredMotion.cs index 06027f59c..6c24be535 100644 --- a/Basis/Packages/com.basis.sdk/Scripts/BasisAuthoredMotion.cs +++ b/Basis/Packages/com.basis.sdk/Scripts/BasisAuthoredMotion.cs @@ -78,6 +78,7 @@ public enum Waveform { Sine, Triangle, Square, Pulse } public Keyframe[] keyframes = Array.Empty(); public BasisMotionClip bakedClip; // shared baked curves; null when using inline keyframes public bool loop = true; + public float sequenceSpeed = 1f; // playback rate multiplier (1 = authored speed); negative plays in reverse // Noise — simplex drift on `channel` about `axis`; reuses amplitude / chain / chainFalloff / seed; `noiseSpeed` = sample rate. public float noiseSpeed = 0.5f; diff --git a/Basis/Packages/com.basis.sdk/Scripts/Editor/BasisAuthoredMotionEditor.cs b/Basis/Packages/com.basis.sdk/Scripts/Editor/BasisAuthoredMotionEditor.cs index 6333c8cda..2d6ff9c21 100644 --- a/Basis/Packages/com.basis.sdk/Scripts/Editor/BasisAuthoredMotionEditor.cs +++ b/Basis/Packages/com.basis.sdk/Scripts/Editor/BasisAuthoredMotionEditor.cs @@ -131,6 +131,7 @@ private void DrawMovementCard(SerializedProperty movements, int i, Field(mv, "bakedClip", "bakedClip"); Field(mv, "sequenceRoot", "sequenceRoot"); Field(mv, "loop", "loop"); + Field(mv, "sequenceSpeed", "sequenceSpeed"); break; } From 6413230ce9f609daaa64a2a1189e72cecb7093d9 Mon Sep 17 00:00:00 2001 From: towneh <25694892+towneh@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:22:52 +0100 Subject: [PATCH 2/2] fix: play reverse one-shot sequences instead of freezing on frame 0 With loop off and a negative speed, st goes negative against a 0-floor clamp, so the playhead pinned to frame 0 and reverse never played. Walk the playhead down from length for reverse one-shots; forward is unchanged. --- .../AuthoredMotion/BasisAuthoredMotionSystem.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs b/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs index b6c14953e..00e9239ea 100644 --- a/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs +++ b/Basis/Packages/com.basis.framework/Drivers/AuthoredMotion/BasisAuthoredMotionSystem.cs @@ -147,8 +147,17 @@ public void Execute(int index, TransformAccess transform) float fr = m.frameRate > 1e-4f ? m.frameRate : 30f; float length = m.frameCount / fr; float st = t * m.sequenceSpeed; - float pt = m.loop != 0 ? math.fmod(st, length) : math.min(math.max(st, 0f), length); - if (pt < 0f) pt += length; // fmod can return negative for negative t + float pt; + if (m.loop != 0) + { + pt = math.fmod(st, length); + if (pt < 0f) pt += length; // fmod can return negative for negative t + } + else + { + // Reverse one-shot walks the playhead down from the end; forward holds at 0..length. + pt = math.clamp(m.sequenceSpeed < 0f ? length + st : st, 0f, length); + } float frameF = pt * fr; int f0 = (int)math.floor(frameF);