Skip to content

Commit

Permalink
Merge pull request #160 from Centurion-Creative-Connect/feature/gun-b…
Browse files Browse the repository at this point in the history
…ullet-preview

GunBullet trajectory prediction
  • Loading branch information
DerpyNewbie authored Mar 29, 2024
2 parents cf7d934 + be4b5fb commit 4b1f885
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using CenturionCC.System.Editor.Utils;
using System;
using System.Linq;
using CenturionCC.System.Editor.Utils;
using CenturionCC.System.Gun;
using CenturionCC.System.Gun.DataStore;
using UdonSharpEditor;
using UnityEditor;
Expand All @@ -10,19 +13,31 @@ namespace CenturionCC.System.Editor.EditorInspector.Gun.DataStore
public class GunBulletDataStoreEditor : UnityEditor.Editor
{
private static bool _usePreview;
private static bool _useRangePreview = true;
private static Transform _shootingRef;
private static float _offsetMin = 1F;
private static float _offsetMax = 5F;
private static int _simPoints = 200;
private float _cachedAvgDistance = 0F;
private float _cachedAvgHighestPoint = 0F;
private int _cachedMaxPatterns = 0;

private bool _hasCache = false;

private void OnSceneGUI()
{
if (!_usePreview || _shootingRef == null) return;
var shootingRef = _shootingRef;
if (!_usePreview) return;

var data = (GunBulletDataStore)target;
serializedObject.ApplyModifiedProperties();
var data = target as GunBulletDataStore;
if (data == null) return;
if (shootingRef == null) shootingRef = data.transform;

if (_useRangePreview) DrawRange(shootingRef, Vector3.zero, Quaternion.identity);
for (int i = (int)_offsetMin; i <= (int)_offsetMax; i++)
{
DrawHandles(_shootingRef, Vector3.zero, Quaternion.identity, data, i);
DrawHandles(shootingRef, Vector3.zero, Quaternion.identity, data, i);
}
}

Expand All @@ -44,17 +59,60 @@ public override void OnInspectorGUI()

so.ApplyModifiedProperties();

// GUILayout.Label("Preview", EditorStyles.boldLabel);
//
// using (new EditorGUI.IndentLevelScope())
// {
// _usePreview = EditorGUILayout.Toggle("Use Preview", _usePreview);
// _shootingRef =
// (Transform)EditorGUILayout.ObjectField("Shooting Ref", _shootingRef, typeof(Transform), true);
// EditorGUILayout.MinMaxSlider("Bullet Count", ref _offsetMin, ref _offsetMax, 1F, 10F);
// if (_usePreview && _shootingRef != null)
// GUILayout.Label($"Previewing pattern {(int)_offsetMin} to {(int)_offsetMax}");
// }
GUILayout.Label("Preview", EditorStyles.boldLabel);

using (new EditorGUI.IndentLevelScope())
{
_usePreview = EditorGUILayout.Toggle("Use Preview", _usePreview);
if (_usePreview)
EditorGUILayout.HelpBox("Preview trajectory is just an approximation, Do not expect accuracy!",
MessageType.Warning);

using (new EditorGUI.DisabledGroupScope(!_usePreview))
{
_useRangePreview = EditorGUILayout.Toggle("Use Range Preview", _useRangePreview);
_shootingRef =
(Transform)EditorGUILayout.ObjectField("Shooting Ref", _shootingRef, typeof(Transform), true);
EditorGUILayout.MinMaxSlider("Bullet Count", ref _offsetMin, ref _offsetMax, 1F, 10F);
_simPoints = EditorGUILayout.IntField("Sim Points", _simPoints);
}

if (_usePreview && _shootingRef != null)
GUILayout.Label($"Previewing pattern {(int)_offsetMin} to {(int)_offsetMax}");

if (GUILayout.Button("Calculate Stats"))
{
var data = target as GunBulletDataStore;
if (data == null) throw new ArgumentNullException(nameof(data));
var recoils = data.RecoilPattern;
var maxPatterns = recoils.MaxPatterns;
var dists = new float[maxPatterns];
var highests = new float[maxPatterns];
for (int i = 0; i < maxPatterns; i++)
{
GetPredictedStats(data, i, out var dist, out var highest);
dists[i] = dist;
highests[i] = highest;
}

_cachedMaxPatterns = maxPatterns;
_cachedAvgDistance = dists.Average();
_cachedAvgHighestPoint = highests.Average();
_hasCache = true;
}

if (_hasCache)
{
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.HelpBox("This is just an approximation. Expect around +-5m error.",
MessageType.Warning);
EditorGUILayout.IntField("Patterns", _cachedMaxPatterns);
EditorGUILayout.FloatField("Avg Distance", _cachedAvgDistance);
EditorGUILayout.FloatField("Avg Highest", _cachedAvgHighestPoint);
}
}
}
}

public static void DrawHandles(Transform parent, Vector3 offsetPos, Quaternion offsetRot,
Expand All @@ -64,10 +122,64 @@ public static void DrawHandles(Transform parent, Vector3 offsetPos, Quaternion o
var pos = l2w.MultiplyPoint3x4(offsetPos);
var rot = offsetRot * l2w.rotation;

var line = GunBullet.PredictTrajectory(pos, rot, data, offset, _simPoints);
var highestPoint = Vector3.negativeInfinity;
var zeroedInPoint = Vector3.negativeInfinity;
for (int i = 1; i < line.Length; i++)
{
var p = line[i];
if (p.y >= highestPoint.y) highestPoint = p;
if (p.y <= pos.y && line[i - 1].y >= pos.y) zeroedInPoint = p;
}

Handles.color = Color.red;
Handles.DrawDottedLine(pos, pos + (rot * Vector3.forward * 2), 10);
// var line = GunBullet.PredictTrajectory(pos, rot, data, offset, 100, 0.02F);
// Handles.DrawPolyLine(line);
Handles.DrawPolyLine(line);

var left = rot * Vector3.left;
var highestPointZeroed = new Vector3(highestPoint.x, pos.y, highestPoint.z);
Handles.color = Color.yellow;
Handles.DrawLine(highestPoint, highestPoint + left);
Handles.Label(highestPoint + left,
$"Highest: {highestPoint.y - pos.y:F2}m ({(highestPointZeroed - pos).magnitude:F2}m)");

Handles.color = Color.green;
Handles.DrawLine(zeroedInPoint, zeroedInPoint + left);
Handles.Label(zeroedInPoint + left, $"Zero-in: {(zeroedInPoint - pos).magnitude:F2}m");
}

public static void DrawRange(Transform parent, Vector3 offsetPos, Quaternion offsetRot, int length = 75,
int part = 5)
{
var parentRot = parent.rotation;
var rot = new Quaternion(0, parentRot.y, 0, parentRot.w).normalized * offsetRot;
var pos = parent.position + offsetRot * offsetPos;
var forward = rot * Vector3.forward;
var left = rot * Vector3.left;

Handles.color = Color.gray;

Handles.DrawLine(pos, pos + forward * length);

Handles.color = Color.white;
for (int i = 0; i < length; i += part)
{
var point = pos + forward * i;
Handles.DrawLine(point, point + left);
Handles.Label(point + left, $"{i}m");
}
}

public static void GetPredictedStats(GunBulletDataStore data, int offset, out float distance, out float highest)
{
var line = GunBullet.PredictTrajectory(Vector3.zero, Quaternion.identity, data, offset, _simPoints);
highest = float.NegativeInfinity;
distance = float.NegativeInfinity;
for (int i = 1; i < line.Length; i++)
{
var p = line[i];
if (p.y >= highest) highest = p.y;
if (p.y <= 0 && line[i - 1].y >= 0) distance = p.magnitude;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using CenturionCC.System.Editor.Utils;
using CenturionCC.System.Gun.DataStore;
using UdonSharpEditor;
using UnityEditor;
using UnityEngine;

namespace CenturionCC.System.Editor.EditorInspector.Gun.DataStore
{
[CustomEditor(typeof(GunRecoilPatternDataStore))]
public class GunRecoilPatternDataStoreEditor : UnityEditor.Editor
{
private int _maxPatterns;

private void OnEnable()
{
_maxPatterns = ((GunRecoilPatternDataStore)target).MaxPatterns;
}

public override void OnInspectorGUI()
{
if (UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target))
return;

GUILayout.Label("Settings", EditorStyles.boldLabel);

var so = serializedObject;
var property = so.GetIterator();
property.NextVisible(true);
while (property.NextVisible(false))
{
GUIUtil.FoldoutPropertyField(property, 2);
}

if (so.ApplyModifiedProperties())
{
_maxPatterns = ((GunRecoilPatternDataStore)target).MaxPatterns;
}

GUILayout.Label("Stats", EditorStyles.boldLabel);
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.IntField("Max Patterns", _maxPatterns);
EditorGUILayout.HelpBox("Max Patterns does not consider duplicated elements", MessageType.Warning);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using CenturionCC.System.Editor.EditorInspector.Gun.DataStore;
using CenturionCC.System.Gun.DataStore;
using UnityEditor;
using UnityEngine;

namespace CenturionCC.System.Editor.EditorWindow
{
public class GunUtilitiesWindow : UnityEditor.EditorWindow
{
private GunBulletDataStore _dataStore;
private Transform _shootingRef;
private bool _showPreview;
private bool _showRange;

private void OnEnable()
{
SceneView.duringSceneGui += OnDrawPreviewGizmos;
}

private void OnDisable()
{
SceneView.duringSceneGui -= OnDrawPreviewGizmos;
}

private void OnGUI()
{
_shootingRef =
EditorGUILayout.ObjectField("Shooting Ref", _shootingRef, typeof(Transform), true) as Transform;
_dataStore =
EditorGUILayout.ObjectField("Data Store", _dataStore, typeof(GunBulletDataStore), true) as
GunBulletDataStore;

_showRange = EditorGUILayout.Toggle("Show Range", _showRange);
_showPreview = EditorGUILayout.Toggle("Show Preview", _showPreview);
}

[MenuItem("Centurion-Utils/Gun Utilities")]
public static void InitMenu()
{
GunUtilitiesWindow window = GetWindow<GunUtilitiesWindow>();
window.titleContent.text = "Gun Utilities";
window.Show();
}

private void OnDrawPreviewGizmos(SceneView sceneView)
{
if (_shootingRef != null && _showPreview)
{
GunBulletDataStoreEditor.DrawRange(_shootingRef, Vector3.zero, Quaternion.identity);
}

if (_shootingRef != null && _dataStore != null && _showPreview)
{
GunBulletDataStoreEditor.DrawHandles(_shootingRef, Vector3.zero, Quaternion.identity, _dataStore);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ [SerializeField] [Tooltip("In seconds, defaults to 5s")]
[SerializeField]
private GunRecoilPatternDataStore recoilPattern;

public GunRecoilPatternDataStore RecoilPattern => recoilPattern;
public override int ProjectileCount => projectileCount;

public override void Get(int i,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using UdonSharp;
using System.Linq;
using System.Runtime.CompilerServices;
using UdonSharp;
using UnityEngine;

[assembly: InternalsVisibleTo("CenturionCC.System.Editor")]

namespace CenturionCC.System.Gun.DataStore
{
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
Expand Down Expand Up @@ -34,6 +38,24 @@ public class GunRecoilPatternDataStore : UdonSharpBehaviour
0.3F
};

public Vector3[] RecoilOffsetPatterns => recoilOffsetPatterns;
public Vector3[] PositionOffsetPatterns => positionOffsetPatterns;
public float[] SpeedOffsetPatterns => speedOffsetPatterns;

#if !COMPILER_UDONSHARP && UNITY_EDITOR

public int MaxPatterns
{
get
{
var nums = new[]
{ recoilOffsetPatterns.Length, positionOffsetPatterns.Length, speedOffsetPatterns.Length };
return nums.Distinct().Aggregate((a, b) => a * b);
}
}

#endif

public virtual Vector3 GetRecoilOffset(int count)
{
return recoilOffsetPatterns[count % recoilOffsetPatterns.Length];
Expand Down
Loading

0 comments on commit 4b1f885

Please sign in to comment.