BITFALL/Assets/Plugins/PluginMaster/DesignTools/Editor/Common/Scripts/GravityUtils.cs

498 lines
20 KiB
C#

/*
Copyright (c) 2020 Omar Duarte
Unauthorized copying of this file, via any medium is strictly prohibited.
Writen by Omar Duarte, 2020.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System.Linq;
using UnityEngine;
namespace PluginMaster
{
[System.Serializable]
public class SimulateGravityData
{
[SerializeField] private int _maxIterations = 1000;
[SerializeField] private Vector3 _gravity = Physics.gravity;
[SerializeField] private float _drag = 0f;
[SerializeField] private float _angularDrag = 0.05f;
[SerializeField] private float _maxSpeed = 100;
private float _maxSpeedSquared = 10000;
[SerializeField] private float _maxAngularSpeed = 10;
private float _maxAngularSpeedSquared = 100;
[SerializeField] private float _mass = 1f;
[SerializeField] private bool _changeLayer = false;
[SerializeField] private int _tempLayer = 0;
[SerializeField] private bool _ignoreSceneColliders = false;
public int maxIterations
{
get => _maxIterations;
set
{
value = Mathf.Clamp(value, 1, 100000);
if (_maxIterations == value) return;
_maxIterations = value;
}
}
public Vector3 gravity
{
get => _gravity;
set
{
if (_gravity == value) return;
_gravity = value;
}
}
public float drag
{
get => _drag;
set
{
value = Mathf.Max(value, 0f);
if (_drag == value) return;
_drag = value;
}
}
public float angularDrag
{
get => _angularDrag;
set
{
value = Mathf.Max(value, 0f);
if (_angularDrag == value) return;
_angularDrag = value;
}
}
public float maxSpeed
{
get => _maxSpeed;
set
{
value = Mathf.Max(value, 0f);
if (_maxSpeed == value) return;
_maxSpeed = value;
_maxSpeedSquared = _maxSpeed * _maxSpeed;
}
}
public float maxAngularSpeed
{
get => _maxAngularSpeed;
set
{
value = Mathf.Max(value, 0f);
if (_maxAngularSpeed == value) return;
_maxAngularSpeed = value;
_maxAngularSpeedSquared = _maxAngularSpeed * _maxAngularSpeed;
}
}
public float maxSpeedSquared => _maxSpeedSquared;
public float maxAngularSpeedSquared => _maxAngularSpeedSquared;
public float mass
{
get => _mass;
set
{
value = Mathf.Max(value, 1e-7f);
if (_mass == value) return;
_mass = value;
}
}
public bool changeLayer
{
get => _changeLayer;
set
{
if (_changeLayer == value) return;
_changeLayer = value;
}
}
public int tempLayer
{
get => _tempLayer;
set
{
if (_tempLayer == value) return;
_tempLayer = value;
}
}
public bool ignoreSceneColliders
{
get => _ignoreSceneColliders;
set
{
if (_ignoreSceneColliders == value) return;
_ignoreSceneColliders = value;
}
}
public void Copy(SimulateGravityData other)
{
_maxIterations = other._maxIterations;
_gravity = other._gravity;
_drag = other._drag;
_angularDrag = other._angularDrag;
_maxSpeed = other._maxSpeed;
_maxSpeedSquared = other._maxSpeedSquared;
_maxAngularSpeed = other._maxAngularSpeed;
_maxAngularSpeedSquared = other._maxAngularSpeedSquared;
_mass = other._mass;
_changeLayer = other._changeLayer;
_tempLayer = other._tempLayer;
_ignoreSceneColliders = other._ignoreSceneColliders;
}
}
public static class GravityUtils
{
private static bool _isPlaying = false;
private static bool _stop = false;
private static void AddCollider(GameObject obj, Mesh mesh, SimulateGravityData data)
{
var collider = MeshUtils.AddCollider(mesh, obj);
if(collider is MeshCollider) (collider as MeshCollider).convex = true;
if (data.changeLayer) obj.layer = data.tempLayer;
}
private static bool IsVisible(GameObject obj)
{
if (obj == null) return false;
var target = obj;
var parentRenderer = target.GetComponentInParent<Renderer>();
var parentTerrain = target.GetComponentInParent<Terrain>();
if (parentRenderer != null) target = parentRenderer.gameObject;
else if (parentTerrain != null) target = parentTerrain.gameObject;
else
{
var parent = target.transform.parent;
if (parent != null)
{
var siblingRenderer = parent.GetComponentInChildren<Renderer>();
var siblingTerrain = parent.GetComponentInChildren<Terrain>();
if (siblingRenderer != null) target = parent.gameObject;
else if (siblingTerrain != null) target = parent.gameObject;
}
}
var renderers = target.GetComponentsInChildren<Renderer>();
if (renderers.Length > 0)
{
foreach (var renderer in renderers)
if (renderer.enabled) return true;
}
var terrains = target.GetComponentsInChildren<Terrain>();
if (terrains.Length > 0)
{
foreach (var terrain in terrains)
if (terrain.enabled) return true;
}
return false;
}
public static Pose[] SimulateGravity(GameObject[] selection, SimulateGravityData simData, bool recordAction)
{
if (_isPlaying && recordAction) return null;
System.Collections.Generic.List<Collider> tempColliders = null;
Collider[] sceneColliders = null;
Collider[] invisibleColliders = null;
if (simData.ignoreSceneColliders)
{
sceneColliders = Object.FindObjectsOfType<Collider>()
.Where(sc => sc.enabled && sc.gameObject.activeInHierarchy && !sc.isTrigger).ToArray();
foreach (var sceneCollider in sceneColliders) sceneCollider.enabled = false;
var sceneMeshFilters = Object.FindObjectsOfType<MeshFilter>()
.Where(mf => mf.gameObject.activeInHierarchy && mf.sharedMesh != null).ToArray();
tempColliders = new System.Collections.Generic.List<Collider>();
foreach (var meshFilter in sceneMeshFilters)
{
var mesh = meshFilter.sharedMesh;
Collider tempCollider = MeshUtils.AddCollider(meshFilter.sharedMesh, meshFilter.gameObject);
if(tempCollider != null) tempColliders.Add(tempCollider);
}
}
else
{
invisibleColliders = Object.FindObjectsOfType<Collider>()
.Where(sc => sc.enabled && sc.gameObject.activeInHierarchy && !IsVisible(sc.gameObject)).ToArray();
foreach (var invisibleCollider in invisibleColliders) invisibleCollider.enabled = false;
}
var originalGravity = Physics.gravity;
Physics.gravity = simData.gravity;
var allBodies = Object.FindObjectsOfType<Rigidbody>();
var originalBodies = new System.Collections.Generic.List<(Rigidbody body, bool useGravity, bool isKinematic,
float drag, float angularDrag, float mass, RigidbodyConstraints constraints,
RigidbodyInterpolation interpolation, CollisionDetectionMode collisionDetectionMode)>();
foreach (var rigidBody in allBodies)
{
originalBodies.Add((rigidBody, rigidBody.useGravity, rigidBody.isKinematic,
rigidBody.drag, rigidBody.angularDrag, rigidBody.mass, rigidBody.constraints,
rigidBody.interpolation, rigidBody.collisionDetectionMode));
rigidBody.useGravity = false;
rigidBody.isKinematic = true;
rigidBody.drag = simData.drag;
rigidBody.angularDrag = simData.angularDrag;
rigidBody.mass = simData.mass;
rigidBody.constraints = RigidbodyConstraints.None;
rigidBody.interpolation = RigidbodyInterpolation.None;
rigidBody.collisionDetectionMode = CollisionDetectionMode.Discrete;
}
var simBodies = new System.Collections.Generic.List<Rigidbody>();
var clones = new GameObject[selection.Length];
var animData = new System.Collections.Generic.List<AnimData>();
void AddColliders(GameObject source, GameObject dest)
{
var meshFilters = source.GetComponents<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
var mesh = meshFilter.sharedMesh;
if (mesh == null) continue;
AddCollider(dest, mesh, simData);
}
var skinnedMeshRenderers = source.GetComponents<SkinnedMeshRenderer>();
foreach (var renderer in skinnedMeshRenderers)
{
var mesh = renderer.sharedMesh;
if (mesh == null) continue;
AddCollider(dest, mesh, simData);
}
foreach (Transform child in source.transform)
{
var destChild = new GameObject();
destChild.transform.SetParent(dest.transform);
destChild.transform.localPosition = child.localPosition;
destChild.transform.localRotation = child.localRotation;
destChild.transform.localScale = child.localScale;
AddColliders(child.gameObject, destChild);
}
}
for (int i = 0; i < selection.Length; ++i)
{
animData.Add(new AnimData(selection[i]));
var obj = new GameObject();
obj.layer = selection[i].layer;
obj.transform.position = selection[i].transform.position;
obj.transform.rotation = selection[i].transform.rotation;
obj.transform.localScale = selection[i].transform.lossyScale;
AddColliders(selection[i], obj);
var magnitude = BoundsUtils.GetMagnitude(selection[i].transform);
selection[i].transform.position += Vector3.up * (100 * magnitude);
var rigidBody = obj.AddComponent<Rigidbody>();
if (simData.changeLayer) obj.layer = simData.tempLayer;
simBodies.Add(rigidBody);
rigidBody.useGravity = true;
rigidBody.isKinematic = false;
clones[i] = obj;
}
#if UNITY_2022_2_OR_NEWER
var prevSimMode = Physics.simulationMode;
Physics.simulationMode = SimulationMode.Script;
#else
Physics.autoSimulation = false;
#endif
for (int i = 0; i < simData.maxIterations; ++i)
{
Physics.Simulate(Time.fixedDeltaTime);
for (int objIdx = 0; objIdx < selection.Length; ++objIdx)
{
var body = simBodies[objIdx];
if (body.velocity.sqrMagnitude > simData.maxSpeedSquared)
body.velocity = body.velocity.normalized * simData.maxSpeed;
if (body.angularVelocity.sqrMagnitude > simData.maxAngularSpeedSquared)
body.angularVelocity = body.angularVelocity.normalized * simData.maxAngularSpeed;
if (i % 10 == 0) animData[objIdx].poses.Add(new Pose(body.position, body.rotation));
}
if (simBodies.All(rb => rb.IsSleeping())) break;
}
#if UNITY_2022_2_OR_NEWER
Physics.simulationMode = prevSimMode;
#else
Physics.autoSimulation = true;
#endif
for (int i = 0; i < selection.Length; ++i)
{
selection[i].transform.position = clones[i].transform.position;
selection[i].transform.rotation = clones[i].transform.rotation;
animData[i].poses.Add(new Pose(selection[i].transform.position, selection[i].transform.rotation));
Object.DestroyImmediate(clones[i]);
}
foreach (var item in originalBodies)
{
if (item.body == null) continue;
item.body.useGravity = item.useGravity;
item.body.isKinematic = item.isKinematic;
item.body.drag = item.drag;
item.body.angularDrag = item.angularDrag;
item.body.mass = item.mass;
item.body.constraints = item.constraints;
item.body.interpolation = item.interpolation;
item.body.collisionDetectionMode = item.collisionDetectionMode;
}
Physics.gravity = originalGravity;
if (simData.ignoreSceneColliders)
{
foreach (var sceneCollider in sceneColliders) sceneCollider.enabled = true;
foreach (var tempCollider in tempColliders) Object.DestroyImmediate(tempCollider);
}
else
{
foreach (var invisibleCollider in invisibleColliders) invisibleCollider.enabled = true;
}
Animate(animData, simData, recordAction);
var finalPoses = new System.Collections.Generic.List<Pose>();
foreach (var d in animData) finalPoses.Add(d.poses.Last());
return finalPoses.ToArray();
}
private class AnimData
{
public GameObject obj = null;
public System.Collections.Generic.List<Pose> poses = new System.Collections.Generic.List<Pose>();
public System.Collections.Generic.List<Collider> enabledColliders
= new System.Collections.Generic.List<Collider>();
public System.Collections.Generic.List<GameObject> tempColliders
= new System.Collections.Generic.List<GameObject>();
public AnimData(GameObject obj) => this.obj = obj;
}
private static void Animate(System.Collections.Generic.List<AnimData> animData,
SimulateGravityData simData, bool recordAction)
{
_stop = false;
_isPlaying = true;
foreach (var item in animData)
{
item.enabledColliders = item.obj.GetComponentsInChildren<Collider>()
.Where(collider => collider.enabled).ToList();
item.tempColliders.Clear();
var temp = new GameObject();
temp.hideFlags = HideFlags.HideAndDontSave;
var lastPose = item.poses.Last();
temp.transform.position = lastPose.position;
temp.transform.rotation = lastPose.rotation;
temp.transform.localScale = item.obj.transform.lossyScale;
var meshFilters = item.obj.GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
var mesh = meshFilter.sharedMesh;
if (mesh == null) continue;
AddCollider(temp, mesh, simData);
}
var skinnedMeshRenderers = item.obj.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var renderer in skinnedMeshRenderers)
{
var mesh = renderer.sharedMesh;
if (mesh == null) continue;
var meshFilter = renderer.gameObject.AddComponent<MeshFilter>();
meshFilter.sharedMesh = mesh;
AddCollider(temp, mesh, simData);
}
item.tempColliders.Add(temp);
foreach (var collider in item.enabledColliders) collider.enabled = false;
}
Animate(animData, 0, recordAction);
}
private async static void Animate(System.Collections.Generic.List<AnimData> data, int frame, bool recordAction)
{
void EnableColliders(AnimData item)
{
foreach (var collider in item.enabledColliders)
{
if (collider == null) continue;
collider.enabled = true;
}
}
void DestroyTempColliders(AnimData item)
{
foreach(var temp in item.tempColliders) UnityEngine.Object.DestroyImmediate(temp);
}
void Record()
{
foreach (var item in data)
{
if (item.obj == null) continue;
EnableColliders(item);
DestroyTempColliders(item);
item.obj.transform.position = item.poses.First().position;
item.obj.transform.rotation = item.poses.First().rotation;
UnityEditor.Undo.RecordObject(item.obj.transform, "Simulate Gravity");
item.obj.transform.position = item.poses.Last().position;
item.obj.transform.rotation = item.poses.Last().rotation;
}
}
var isPlaying = false;
if (_stop)
{
if (recordAction) Record();
return;
}
foreach (var item in data)
{
if (item.obj == null) break;
if (frame >= item.poses.Count)
{
item.obj.transform.position = item.poses.Last().position;
item.obj.transform.rotation = item.poses.Last().rotation;
continue;
}
isPlaying = true;
item.obj.transform.position = item.poses[frame].position;
item.obj.transform.rotation = item.poses[frame].rotation;
}
if (isPlaying)
{
await System.Threading.Tasks.Task.Delay((int)(Time.fixedDeltaTime * 1000));
Animate(data, frame + 1, recordAction);
}
else
{
if (recordAction) Record();
else
{
foreach (var item in data)
{
if (item.obj == null) continue;
EnableColliders(item);
DestroyTempColliders(item);
}
}
_isPlaying = false;
}
}
[UnityEditor.InitializeOnLoad]
private static class UndoEventHandler
{
static UndoEventHandler() => UnityEditor.Undo.undoRedoPerformed += OnUndoRedoPerformed;
private static void OnUndoRedoPerformed()
{
_isPlaying = false;
_stop = true;
}
}
}
}