Last active
August 24, 2023 13:52
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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