Skip to content

Instantly share code, notes, and snippets.

@SolarianZ
Last active August 24, 2023 13:52
Show Gist options
  • Save SolarianZ/a53363f0994aa7858d9e0a08ecf3f014 to your computer and use it in GitHub Desktop.
Save SolarianZ/a53363f0994aa7858d9e0a08ecf3f014 to your computer and use it in GitHub Desktop.
{"category": "Unity/Runtime/Utility/Math", "keywords": "Math, Physics, Simple Harmonic Motion, SHM, Second Order System"} Simple Harmonic Motion Visualizer.
// NOTE: The 'SimpleHarmonicMotion.cs' is here: https://gist.github.com/SolarianZ/78f9b22d9663d77b6e6c1b0c60cc6322
using UnityEngine;
using SVector3 = System.Numerics.Vector3;
using UVector3 = UnityEngine.Vector3;
#if UNITY_EDITOR
using UnityEditor;
#endif
[DisallowMultipleComponent]
public class SimpleHarmonicMotionTest : MonoBehaviour
{
[Range(0, 5)]
public float frequency = 5;
[Range(0, 2)]
public float damping = 0.15f;
public bool drawTargetTrack = true;
public bool drawFollowerTrack = true;
public Color targetTrackColor = Color.yellow;
public Color followerTrackColor = Color.cyan;
[Range(0f, 5f)]
public float trackDuration = 1f;
public bool trackDepthTest;
public bool setRandomDest;
private Transform _target;
private Transform _follower;
private SimpleHarmonicMotion _shm;
private UVector3 _lastTargetPos;
private UVector3 _lastFollowerPos;
private void Start()
{
_target = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
_target.name = "[Target]";
_target.parent = transform;
_target.localScale = UVector3.one * 0.05f;
_follower = GameObject.CreatePrimitive(PrimitiveType.Cube).transform;
_follower.name = "[Follower]";
_follower.parent = transform;
_follower.localScale = UVector3.one * 0.05f;
var initialPosition = _follower.position;
_lastTargetPos = initialPosition;
_lastFollowerPos = initialPosition;
_shm = new SimpleHarmonicMotion(frequency, damping, Convert(initialPosition));
}
private void Update()
{
if (setRandomDest)
{
setRandomDest = false;
var hDir = Random.insideUnitCircle.normalized;
_target.position = new UVector3(hDir.x, 0, hDir.y) * 2f;
}
// Follow
_shm.Frequency = frequency;
_shm.Damping = damping;
var newTargetPos = _target.position;
var newFollowerPos = Convert(_shm.Tick(Time.deltaTime, Convert(newTargetPos)));
_follower.position = newFollowerPos;
// Track
if (drawTargetTrack)
{
Debug.DrawLine(_lastTargetPos, newTargetPos, targetTrackColor, trackDuration, trackDepthTest);
_lastTargetPos = newTargetPos;
}
if (drawFollowerTrack)
{
Debug.DrawLine(_lastFollowerPos, newFollowerPos, followerTrackColor, trackDuration, trackDepthTest);
_lastFollowerPos = newFollowerPos;
}
}
public static UVector3 Convert(SVector3 value) => new UVector3(value.X, value.Y, value.Z);
public static SVector3 Convert(UVector3 value) => new SVector3(value.x, value.y, value.z);
#if UNITY_EDITOR
[CustomEditor(typeof(SimpleHarmonicMotionTest))]
class Editor : UnityEditor.Editor
{
private const float AXIS_H = 10f;
private const float AXIS_V = 1f;
private SimpleHarmonicMotionTest _target => (SimpleHarmonicMotionTest)target;
private SimpleHarmonicMotion _shm;
private void OnEnable()
{
_shm = new SimpleHarmonicMotion(_target.frequency, _target.damping, default);
}
public override bool HasPreviewGUI()
{
return true;
}
public override GUIContent GetPreviewTitle()
{
var title = base.GetPreviewTitle();
title.text = ObjectNames.NicifyVariableName(nameof(SimpleHarmonicMotion)) + " Preview";
return title;
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
base.OnPreviewGUI(r, background);
DrawShmGraph(r);
}
private void DrawShmGraph(Rect area, float padding = 4f)
{
// Padding
area.x += padding;
area.y += padding;
area.width -= padding * 2;
area.height -= padding * 2;
// Axes
Handles.color = Color.green;
Handles.DrawLine(area.position, area.position + new Vector2(0, area.height));
Handles.DrawLine(area.position + new Vector2(0, area.height), area.position + new Vector2(area.width, area.height));
// Points
const short STEPS = 2000;
var startPos = UVector3.zero;
var targetPos = startPos + UVector3.up * AXIS_V + UVector3.right * AXIS_H;
var lastPoint = WorldToShmGraphSpace(area, startPos);
_shm.Frequency = _target.frequency;
_shm.Damping = _target.damping;
_shm.ResetPosition(SimpleHarmonicMotionTest.Convert(startPos));
var interval = AXIS_H / STEPS;
for (short i = 0; i < STEPS; i++)
{
var linePos = SimpleHarmonicMotionTest.Convert(_shm.Tick(interval, SimpleHarmonicMotionTest.Convert(targetPos)));
var x = interval * i * AXIS_H;
var y = (linePos - startPos).magnitude / (targetPos - startPos).magnitude;
var point = WorldToShmGraphSpace(area, new UVector3(x, y, 0));
Handles.DrawLine(lastPoint, point);
lastPoint = point;
}
}
private UVector3 WorldToShmGraphSpace(Rect graphArea, UVector3 point)
{
var ratioX = point.x / AXIS_H;
var ratioY = point.y / AXIS_V;
var posX = graphArea.position.x + graphArea.width * ratioX;
var posY = graphArea.position.y + graphArea.height - graphArea.height * ratioY / 2f;
var graphPoint = new UVector3(posX, posY, 0);
return graphPoint;
}
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment