Skip to content

Instantly share code, notes, and snippets.

@maluoi
Last active June 4, 2024 04:35
Show Gist options
  • Save maluoi/d65f35a90c1129cac2856e25f745f9a2 to your computer and use it in GitHub Desktop.
Save maluoi/d65f35a90c1129cac2856e25f745f9a2 to your computer and use it in GitHub Desktop.
A basic tweening / lerping library for StereoKit.
// SPDX-License-Identifier: MIT
// The authors below grant copyright rights under the MIT license:
// Copyright (c) 2024 Nick Klingensmith
using System;
namespace StereoKit.Framework
{
/// <summary> Don't know which to use? Try SoftOut! It's a great default!
/// This site is a great reference for how these functions look:
/// https://easings.net/
/// </summary>
public static class DynEase
{
/// <summary>A constant motion the whole way through. Stops and starts
/// hard, doesn't look all that great in most cases.</summary>
public static float Linear (float t) => t;
/// <summary>Quadratic soft start with a hard stop.</summary>
public static float SoftIn (float t) => t * t;
/// <summary>Quadratic fast start with a soft stop, a very good default
/// for most cases as it feels very responsive and looks good.</summary>
public static float SoftOut (float t) => 1 - (1 - t) * (1 - t);
/// <summary>Quadratic smooth start, fast middle, and smooth stop.
/// </summary>
public static float SoftInOut (float t) => t < 0.5f ? 2 * t * t : 1 - (float)Math.Pow(-2 * t + 2, 2) / 2;
/// <summary>Quartic soft start with a hard stop.</summary>
public static float FastIn (float t) => t * t * t * t;
/// <summary>Quartic fast start with a soft stop, a pretty good default
/// for most cases as it feels very responsive and looks good.</summary>
public static float FastOut (float t) => 1 - (float)Math.Pow(1 - t, 4);
/// <summary>Quartic smooth start, fast middle, and smooth stop.
/// </summary>
public static float FastInOut (float t) => t < 0.5 ? 8 * t * t * t * t : 1 - (float)Math.Pow(-2 * t + 2, 4) / 2;
const float overshoot = 1.70158f;
const float overshoot2 = overshoot + 1;
/// <summary>Soft start with a hard stop, overshoots its destination a
/// bit before arriving at it.</summary>
public static float OvershootIn (float t) => overshoot2 * t * t * t - overshoot * t * t;
/// <summary>Fast start with a soft stop, backs off a bit before moving
/// to its destination.</summary>
public static float OvershootOut (float t) => 1 + overshoot2 * (float)Math.Pow(t - 1, 3) + overshoot * (float)Math.Pow(t - 1, 2);
/// <summary>Smooth start, fast middle, and smooth stop. Overshoots on
/// both the start and the end.</summary>
public static float OvershootInOut(float t) => t < 0.5
? ((float)Math.Pow(2 * t, 2) * ((overshoot2 + 1) * 2 * t - overshoot2)) / 2
: ((float)Math.Pow(2 * t - 2, 2) * ((overshoot2 + 1) * (t * 2 - 2) + overshoot2) + 2) / 2;
}
public delegate float EaseFn(float t);
public class DynVec3
{
Vec3 begin, end;
float start, duration;
EaseFn ease = DynEase.Linear;
public bool IsFinished => Time.Totalf - start >= duration;
public DynVec3(Vec3 pt) => SetTo(pt);
public DynVec3(float x, float y, float z) => SetTo(new Vec3(x,y,z));
public Vec3 Resolve()
{
float t = Math.Min(1, (Time.Totalf - start) / duration);
return Vec3.Lerp(begin, end, ease(t));
}
public void AnimTo(float x, float y, float z, float duration, EaseFn easeFn) => AnimTo(new Vec3(x,y,z), duration, easeFn );
public void AnimTo(Vec3 pt, float duration, EaseFn easeFn) { begin = Resolve(); start = Time.Totalf; end = pt; ease = easeFn; this.duration = duration; }
public void SetTo (Vec3 pt) { begin = pt; start = 0; end = pt; ease = DynEase.Linear; this.duration = 1; }
public static implicit operator Vec3(DynVec3 v) => v.Resolve();
}
public class DynColor
{
Color begin, end;
float start, duration;
EaseFn ease = DynEase.Linear;
public bool IsFinished => Time.Totalf - start >= duration;
public DynColor(Color color) => SetTo(color);
public DynColor(float r, float g, float b, float a=1) => SetTo(new Color(r,g,b,a));
public Color Resolve()
{
float t = Math.Min(1, (Time.Totalf - start) / duration);
return Color.Lerp(begin, end, ease(t));
}
public void AnimTo(Color color, float duration, EaseFn easeFn) { begin = Resolve(); start = Time.Totalf; end = color; ease = easeFn; this.duration = duration; }
public void SetTo (Color color) { begin = color; start = 0; end = color; ease = DynEase.Linear; this.duration = 1; }
public static implicit operator Color(DynColor color) => color.Resolve();
}
public class DynPose
{
Pose begin, end;
float start, duration;
EaseFn ease = DynEase.Linear;
public bool IsFinished => Time.Totalf - start >= duration;
public DynPose(Pose pose) => SetTo(pose);
public DynPose(Vec3 pos, Quat rot) => SetTo(new Pose(pos, rot));
public Pose Resolve()
{
float t = Math.Min(1, (Time.Totalf - start) / duration);
return Pose.Lerp(begin, end, ease(t));
}
public void AnimTo(Pose pose, float duration, EaseFn easeFn) { begin = Resolve(); start = Time.Totalf; end = pose; ease = easeFn; this.duration = duration; }
public void SetTo (Pose pose) { begin = pose; start = 0; end = pose; ease = DynEase.Linear; this.duration = 1; }
public static implicit operator Pose(DynPose pose) => pose.Resolve();
}
}
if (!SK.Initialize())
return;
Random r = new ();
DynVec3 pos = new (0,0,-0.5f);
DynColor col = new (Color.White);
EaseFn[] eases = new EaseFn[] {
DynEase.SoftOut,
DynEase.FastOut,
DynEase.SoftInOut,
DynEase.OvershootInOut };
SK.Run(() =>
{
Mesh.Sphere.Draw(Material.Default, Matrix.TS(pos, 0.1f), col);
if (Input.Key(Key.Space).IsJustActive())
{
float x = (r.NextSingle() - 0.5f) * 0.5f;
float y = (r.NextSingle() - 0.5f) * 0.5f;
float z = -0.5f - (r.NextSingle() - 0.5f) * 0.5f;
Color color = Color.HSV(r.NextSingle(), 0.8f, 1);
EaseFn ease = eases[r.Next(eases.Length)];
pos.AnimTo(x, y, z, 0.5f, ease);
col.AnimTo(color, 0.5f, ease);
}
});
@maluoi
Copy link
Author

maluoi commented Jun 4, 2024

This can now be found in StereoKit.Framework with a slightly different name. Ease, EaseVec3, EaseColor, and EasePose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment