From baf8ef835415eeac643a37c75d9190026a7d6da7 Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:03:02 +0800 Subject: [PATCH 01/10] fix: do not grab the air --- Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs index f4c8aa1..1610e7b 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs @@ -34,11 +34,13 @@ private void Start() public void GrabWithLeftHand(float weight) { _leftHand.enabled = weight < leftHandBearWeight; + _leftHand.enabled = weight < leftHandBearWeight && weight > 0.1f; } public void GrabWithRightHand(float weight) { _rightHand.enabled = weight < rightHandBearWeight; + _rightHand.enabled = weight < rightHandBearWeight && weight > 0.1f; } } From b40727d089ac0cd370083a16d917881a584e9dac Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:44:27 +0800 Subject: [PATCH 02/10] rm old code --- Assets/Scripts/Gameplay/Fracture.meta | 3 - Assets/Scripts/Gameplay/Fracture/Fracture.cs | 16 -- .../Gameplay/Fracture/Fracture.cs.meta | 3 - .../Scripts/Gameplay/Fracture/Fragment.meta | 3 - .../Gameplay/Fracture/Fragment/MeshVertex.cs | 57 ---- .../Fracture/Fragment/MeshVertex.cs.meta | 3 - Assets/Scripts/Gameplay/Fracture/Options.meta | 3 - .../Fracture/Options/CallbackOptions.cs | 17 -- .../Fracture/Options/CallbackOptions.cs.meta | 3 - .../Fracture/Options/FractureOptions.cs | 28 -- .../Fracture/Options/FractureOptions.cs.meta | 3 - .../Scripts/Gameplay/Fracture/Utilities.meta | 3 - .../Gameplay/Fracture/Utilities/BinSort.cs | 101 -------- .../Fracture/Utilities/BinSort.cs.meta | 3 - .../Gameplay/Fracture/Utilities/MathUtils.cs | 131 ---------- .../Fracture/Utilities/MathUtils.cs.meta | 3 - .../Gameplay/Fracture/Utilities/MeshUtils.cs | 245 ------------------ .../Fracture/Utilities/MeshUtils.cs.meta | 3 - .../Fracture/Utilities/Vector3Extensions.cs | 26 -- .../Utilities/Vector3Extensions.cs.meta | 3 - 20 files changed, 657 deletions(-) delete mode 100644 Assets/Scripts/Gameplay/Fracture.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Fracture.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Fracture.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Fragment.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Options.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs delete mode 100644 Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs.meta diff --git a/Assets/Scripts/Gameplay/Fracture.meta b/Assets/Scripts/Gameplay/Fracture.meta deleted file mode 100644 index a3f5529..0000000 --- a/Assets/Scripts/Gameplay/Fracture.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: b6169bea461d4144b1dd869e6910039d -timeCreated: 1659002525 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Fracture.cs b/Assets/Scripts/Gameplay/Fracture/Fracture.cs deleted file mode 100644 index 96c56d6..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Fracture.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Gameplay.Fracture.Options; -using UnityEngine; - -namespace Gameplay.Fracture -{ - // Fracture Component - public class Fracture : MonoBehaviour - { - // --- Inspector Options --- - public FractureOptions fractureOptions; - public CallbackOptions callbackOptions; - - - - } -} diff --git a/Assets/Scripts/Gameplay/Fracture/Fracture.cs.meta b/Assets/Scripts/Gameplay/Fracture/Fracture.cs.meta deleted file mode 100644 index c601dda..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Fracture.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: dcb81d28944043aaab3befb91e9428ca -timeCreated: 1659337623 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Fragment.meta b/Assets/Scripts/Gameplay/Fracture/Fragment.meta deleted file mode 100644 index 5ac853f..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Fragment.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 093e10fb8685415b8bc67fb8ab4a2dd1 -timeCreated: 1659349941 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs b/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs deleted file mode 100644 index d824a0a..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs +++ /dev/null @@ -1,57 +0,0 @@ -using UnityEngine; -using UnityEngine.TestTools; - -namespace Gameplay.Fracture.Fragment -{ - /// - /// Data structure containing position/normal/UV data for a single vertex - /// - public struct MeshVertex - { - public Vector3 position; - public Vector3 normal; - public Vector2 uv; - - public MeshVertex(Vector3 position) - { - this.position = position; - this.normal = Vector3.zero; - this.uv = Vector2.zero; - } - - public MeshVertex(Vector3 position, Vector3 normal, Vector2 uv) - { - this.position = position; - this.normal = normal; - this.uv = uv; - } - - public override bool Equals(object obj) - { - if (!(obj is MeshVertex)) return false; - - return ((MeshVertex)obj).position.Equals(this.position); - } - - public static bool operator ==(MeshVertex lhs, MeshVertex rhs) - { - return lhs.Equals(rhs); - } - - public static bool operator !=(MeshVertex lhs, MeshVertex rhs) - { - return !lhs.Equals(rhs); - } - - public override int GetHashCode() - { - return this.position.GetHashCode(); - } - - [ExcludeFromCoverage] - public override string ToString() - { - return $"Position = {position}, Normal = {normal}, UV = {uv}"; - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs.meta b/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs.meta deleted file mode 100644 index 14b9626..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Fragment/MeshVertex.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 2d94a76aeb3149839e3bd515c749cb4d -timeCreated: 1659349954 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Options.meta b/Assets/Scripts/Gameplay/Fracture/Options.meta deleted file mode 100644 index 7249687..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Options.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: ae87cf4676ab4f6cad605e38c3c94b98 -timeCreated: 1659338162 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs b/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs deleted file mode 100644 index 613caf6..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Events; - -namespace Gameplay.Fracture.Options -{ - [Serializable] - public class CallbackOptions - { - public UnityEvent onCompleted; - - public CallbackOptions() - { - this.onCompleted = null; - } - } -} diff --git a/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs.meta deleted file mode 100644 index e3f5203..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Options/CallbackOptions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: a8fd5b80afcd42d6bbb6eedafd2738dc -timeCreated: 1659338528 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs b/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs deleted file mode 100644 index a492233..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using UnityEngine; - -namespace Gameplay.Fracture.Options -{ - public enum TriggerType - { - Collision, - } - - [Serializable] - public class FractureOptions - { - [Range(1, 100)] - public int maxFractures; - public bool detectFloatingFragment; - public TriggerType triggerType; - public float minCollisionForce; - - FractureOptions() - { - maxFractures = 50; - detectFloatingFragment = true; - triggerType = TriggerType.Collision; - minCollisionForce = 1f; - } - } -} diff --git a/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs.meta deleted file mode 100644 index b71ea3f..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Options/FractureOptions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: ae76b77e16ad4e2ea8779b50ddaf59b6 -timeCreated: 1659338198 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities.meta b/Assets/Scripts/Gameplay/Fracture/Utilities.meta deleted file mode 100644 index dd6f1da..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 751ce9a88a2c426ea755f4013af9523e -timeCreated: 1659349711 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs b/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs deleted file mode 100644 index e343c0d..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs +++ /dev/null @@ -1,101 +0,0 @@ -// This script is originated from https://github.com/dgreenheck/OpenFracture - -namespace Gameplay.Fracture.Utilities -{ - /// - /// Defines an interface for an object that is sorted by bin number - /// - public interface IBinSortable - { - int bin { get; set; } - } - - /// - /// Methods for sorting objects on an ordered grid by bin number. - /// - /// The grid ordering is shown by example below. Even rows (row 0 = bottom row) are ordered - /// right-to-left while odd rows are ordered left-to-right. - /// _____ _____ _____ - /// | | | | - /// | 6 | 7 | 8 | - /// |_____|_____|_____| - /// | | | | - /// | 5 | 4 | 3 | - /// |_____|_____|_____| - /// | | | | - /// | 0 | 1 | 2 | - /// |_____|_____|_____| - /// - /// - public class BinSort - { - /// - /// Computes the bin number for the set of grid coordinates - /// - /// Grid row - /// Grid column - /// Grid size - /// - internal static int GetBinNumber(int i, int j, int n) - { - return (i % 2 == 0) ? (i * n) + j : (i + 1) * n - j - 1; - } - - /// - /// Performs a counting sort of the input points based on their bin number. Only - /// sorts the elements in the index range [0, count]. If binCount is <= 1, no sorting - /// is performed. If lastIndex > input.Length, the entire input array is sorted. - /// - /// The input array to sort - /// The index of the last element in `input` to sort. Only the - /// elements [0, lastIndex) are sorted. - /// Number of bins - internal static T[] Sort(T[] input, int lastIndex, int binCount) where T: IBinSortable - { - int[] count = new int[binCount]; - T[] output = new T[input.Length]; - - #region Validation - // Need at least two bins to sort - if (binCount <= 1) - { - return input; - } - - // If lastIndex is out of range, default to sorting the entire input array - if (lastIndex > input.Length) - { - lastIndex = input.Length; - } - #endregion - - // Only sort the first [0, count] points, don't want to sort super-triangle vertices - for (int i = 0; i < lastIndex; i++) - { - int j = input[i].bin; - count[j] += 1; - } - - for (int i = 1; i < binCount; i++) - { - count[i] += count[i - 1]; - } - - for (int i = lastIndex - 1; i >= 0; i--) - { - int j = input[i].bin; - count[j] -= 1; - output[count[j]] = input[i]; - } - - // Copy over the rest of the un-sorted points - for (int i = lastIndex; i < output.Length; i++) - { - output[i] = input[i]; - } - - return output; - } - - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs.meta b/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs.meta deleted file mode 100644 index 1e7900e..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/BinSort.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 290d7c7c596c4542928bc9ac6fed2bb8 -timeCreated: 1659349711 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs b/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs deleted file mode 100644 index 41ef1ea..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs +++ /dev/null @@ -1,131 +0,0 @@ -// This script is originated from https://github.com/dgreenheck/OpenFracture - -using UnityEngine; - -namespace Gameplay.Fracture.Utilities -{ - public static class MathUtils - { - /// - /// Returns true if the quad specified by the two diagonals a1->a2 and b1->b2 is convex - /// Quad is convex if a1->a2 and b1->b2 intersect each other - /// - /// Start point of diagonal A - /// End point of diagonal A - /// Start point of diagonal B - /// End point of diagonal B - /// - public static bool IsQuadConvex(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2) - { - return LinesIntersectInternal(a1, a2, b1, b2, true); - } - - /// - /// Returns true lines a1->a2 and b1->b2 is intersect - /// - /// Start point of line A - /// End point of line A - /// Start point of line B - /// End point of line B - /// - public static bool LinesIntersect(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2) - { - return LinesIntersectInternal(a1, a2, b1, b2, false); - } - - /// - /// Returns true lines a1->a2 and b1->b2 is intersect - /// - /// Start point of line A - /// End point of line A - /// Start point of line B - /// End point of line B - /// - private static bool LinesIntersectInternal(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, bool includeSharedEndpoints) - { - Vector2 a12 = new Vector2(a2.x - a1.x, a2.y - a1.y); - Vector2 b12 = new Vector2(b2.x - b1.x, b2.y - b1.y); - - // If any of the vertices are shared between the two diagonals, - // the quad collapses into a triangle and is convex by default. - if (a1 == b1 || a1 == b2 || a2 == b1 || a2 == b2) - { - return includeSharedEndpoints; - } - else - { - // Compute cross product between each point and the opposite diagonal - // Look at sign of the Z component to see which side of line point is on - float a1xb = (a1.x - b1.x) * b12.y - (a1.y - b1.y) * b12.x; - float a2xb = (a2.x - b1.x) * b12.y - (a2.y - b1.y) * b12.x; - float b1xa = (b1.x - a1.x) * a12.y - (b1.y - a1.y) * a12.x; - float b2xa = (b2.x - a1.x) * a12.y - (b2.y - a1.y) * a12.x; - - // Check that the points for each diagonal lie on opposite sides of the other - // diagonal. Quad is also convex if a1/a2 lie on b1->b2 (and vice versa) since - // the shape collapses into a triangle (hence >= instead of >) - return ((a1xb >= 0 && a2xb <= 0) || (a1xb <= 0 && a2xb >= 0)) && - ((b1xa >= 0 && b2xa <= 0) || (b1xa <= 0 && b2xa >= 0)); - } - } - - /// - /// Determines the intersection between the line segment a->b and the plane defined by the specified normal and origin point. If an intersection point exists, it is returned via the out parameter `intersection`. The parameter `s` is defined below and is used to properly interpolate normals/uvs for intersection vertices. - /// - /// Start point of line - /// End point of line - /// Plane normal - /// Plane origin - /// If intersection exists, intersection point return as out parameter. - /// Returns the parameterization of the intersection where x = a + (b - a) * s - /// - public static bool LinePlaneIntersection(Vector3 a, - Vector3 b, - Vector3 n, - Vector3 p0, - out Vector3 x, - out float s) - { - // Initialize out params - s = 0; - x = Vector3.zero; - - // Handle degenerate cases - if (a == b) - { - return false; - } - else if (n == Vector3.zero) - { - return false; - } - - // `s` is the parameter for the line segment a -> b where 0.0 <= s <= 1.0 - s = Vector3.Dot(p0 - a, n) / Vector3.Dot(b - a, n); - - if (s >= 0 && s <= 1) - { - x = a + (b - a) * s; - return true; - } - - return false; - } - - /// - /// Returns true of the point `p` is on the left side of the directed line segment `i` -> `j` - /// Use for checking if a point is inside of a triangle. Since triangle vertices oriented - /// CCW, a point on the left side of a triangle edge is "inside" that edge of the triangle. - /// - /// Index of test point in `points` array - /// Index of first vertex of the edge in the `points` array - /// /// Index of second vertex of the edge in the `points` array - /// True if the point `p` is on the left side of the line `i`->`j` - public static bool IsPointOnRightSideOfLine(Vector2 a, Vector2 b, Vector2 c) - { - // The <= is essential; if it is <, the whole thing falls apart - return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0; - } - - } -} diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs.meta b/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs.meta deleted file mode 100644 index 6bc9dd7..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/MathUtils.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0581da0036ee48c1ae1048744d0ef642 -timeCreated: 1659349711 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs b/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs deleted file mode 100644 index 7bee951..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs +++ /dev/null @@ -1,245 +0,0 @@ -// This script is originated from https://github.com/dgreenheck/OpenFracture - -using System.Collections.Generic; -using Gameplay.Fracture.Fragment; -using Unity.Collections; -using UnityEngine; -using UnityEngine.Rendering; - -namespace Gameplay.Fracture.Utilities -{ - public static class MeshUtils - { - // Description of vertex attributes for the island mesh - private static VertexAttributeDescriptor[] layout = new[] - { - new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), - new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), - new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2), - }; - - /// - /// Identifies all disconnected sets of geometry contained within the mesh. - /// Each set of geometry is split into a separate meshes. - /// - /// The mesh to search - /// Returns an array of all disconnected meshes found. - public static Mesh[] FindDisconnectedMeshes(Mesh mesh) - { - // Each disconnected set of geometry is referred to as an "island" - List islands = new List(); - - #region Preliminaries - - // Extract mesh data - var vertices = mesh.vertices; - var triangles = mesh.triangles; - var normals = mesh.normals; - var uvs = mesh.uv; - - // For each triangle, find the corresponding sub-mesh index. (Mesh.triangles contains - // the triangles for all sub-meshes) - int[] triangleSubMesh = new int[triangles.Length / 3]; - int subMeshIndex = 0; - int subMeshSize = mesh.GetTriangles(subMeshIndex).Length / 3; - for (int i = 0; i < triangles.Length / 3; i++) - { - if (i >= subMeshSize) - { - subMeshIndex++; - subMeshSize += mesh.GetTriangles(subMeshIndex).Length / 3; - } - triangleSubMesh[i] = subMeshIndex; - } - - // Identify coincident vertices - List[] coincidentVertices = new List[vertices.Length]; - for(int i = 0; i < vertices.Length; i++) - { - coincidentVertices[i] = new List(); - } - for(int i = 0; i < vertices.Length; i++) - { - Vector3 v_i = vertices[i]; - for (int k = i + 1; k < vertices.Length; k++) - { - Vector3 v_k = vertices[k]; - if (v_i == v_k) - { - coincidentVertices[k].Add(i); - coincidentVertices[i].Add(k); - } - } - } - - // Find the triangles the each vertex belongs to. Need to do this for each submesh - List[] vertexTriangles = new List[vertices.Length]; - - for (int i = 0; i < vertices.Length; i++) - { - vertexTriangles[i] = new List(); - } - - int v1, v2, v3; - for (int i = 0; i < triangles.Length; i += 3) - { - // Index of the triangle - int t = i / 3; - - v1 = triangles[i]; - v2 = triangles[i + 1]; - v3 = triangles[i + 2]; - - vertexTriangles[v1].Add(t); - vertexTriangles[v2].Add(t); - vertexTriangles[v3].Add(t); - } - - #endregion - - // Search the mesh geometry and identify all islands - // 1) Start by finding a vertex that has not yet been visited - // 2) Insert the vertex into a queue, begin a breadth-first search - // 3) Dequeue the next vertex 'v' - // 4) Find all triangles that 'v' is connected to. Add each triangle to a list - // 5) Enqueue the vertices for each connected triangle if they haven't been visited yet - // 6) Enqueue all vertices coincident with 'v' if they haven't been visited yet - // 7) Repeat Steps 3-6 until the queue is empty - // 8) Take the list of triangles and use the existing mesh data to create a new island mesh - // 9) Go back to Step 1, continue until all vertices have been visited. - - bool[] visitedVertices = new bool[vertices.Length]; - bool[] visitedTriangles = new bool[triangles.Length]; - Queue frontier = new Queue(); - - // Vertex data for the island mesh. Only initialize once and keep track of pointer to last element to minimize GC - NativeArray islandVertices = new NativeArray(vertices.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - // Array containing triangle data for the island mesh. Need to keep track of triangles for each sub-mesh separately - int[][] islandTriangles = new int[mesh.subMeshCount][]; - for (int i = 0; i < mesh.subMeshCount; i++) - { - islandTriangles[i] = new int[triangles.Length]; - } - - // Counters to keep track of how many vertices - int vertexCount = 0; - int totalIndexCount = 0; - int[] subMeshIndexCounts = new int[mesh.subMeshCount]; - - for (int i = 0; i < vertices.Length; i++) - { - if (visitedVertices[i]) continue; - - // Reset the vertex/triangle counts - vertexCount = 0; - totalIndexCount = 0; - for(int j = 0; j < mesh.subMeshCount; j++) - { - subMeshIndexCounts[j] = 0; - } - - // Search the mesh geometry starting at vertex 'i'. Search is performed by looking up - // the triangles that contain each vertex, adding their vertices, etc. until all - // triangles have been visited. - frontier.Enqueue(i); - - // Index map between source mesh vertex array and the sub mesh vertex arrays - int[] vertexMap = new int[vertices.Length]; - // Initialize map to '-1' to serve as "unmapped" value - for(int j = 0; j < vertices.Length; j++) - { - vertexMap[j] = -1; - } - - while (frontier.Count > 0) - { - int k = frontier.Dequeue(); - - // Ignore vertex if we've already visited it - if (visitedVertices[k]) - { - continue; - } - else - { - visitedVertices[k] = true; - } - - // Add this vertex array for the island mesh - // Map between the original vertex index to the vertex's new index in the island - // mesh vertex array. This will be used to update the indices for the triangles later - vertexMap[k] = vertexCount; - islandVertices[vertexCount++] = new MeshVertex(vertices[k], normals[k], uvs[k]); - - // Get the list of all triangles that this vertex is a part of - foreach(int t in vertexTriangles[k]) - { - // If triangle is already included, skip it - if (!visitedTriangles[t]) - { - visitedTriangles[t] = true; - - // Loop through each vertex of the triangle and add the non-visited ones - // to the search frontier - for (int m = t * 3; m < t * 3 + 3; m++) - { - int v = triangles[m]; - subMeshIndex = triangleSubMesh[t]; - islandTriangles[subMeshIndex][subMeshIndexCounts[subMeshIndex]++] = v; - totalIndexCount++; - - frontier.Enqueue(v); - - // If this vertex is coincident with other vertices, add those to the search frontier - foreach(int cv in coincidentVertices[v]) - { - frontier.Enqueue(cv); - } - } - } - } - } - - // If the island contains at least one triangle, create a new mesh - if (vertexCount > 0) - { - Mesh island = new Mesh(); - - island.SetIndexBufferParams(totalIndexCount, IndexFormat.UInt32); - island.SetVertexBufferParams(vertexCount, layout); - island.SetVertexBufferData(islandVertices, 0, 0, vertexCount); - - // Set the triangles for each submesh - island.subMeshCount = mesh.subMeshCount; - int indexStart = 0; - for (subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) - { - var subMeshIndexBuffer = islandTriangles[subMeshIndex]; - var subMeshIndexCount = subMeshIndexCounts[subMeshIndex]; - - // Map vertex indexes from the original mesh to the island mesh - for(int k = 0; k < subMeshIndexCount; k++) - { - int originalIndex = subMeshIndexBuffer[k]; - subMeshIndexBuffer[k] = vertexMap[originalIndex]; - } - - // Set the index data for this sub mesh - island.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, (int)subMeshIndexCount); - island.SetSubMesh(subMeshIndex, new SubMeshDescriptor(indexStart, subMeshIndexCount)); - - indexStart += subMeshIndexCount; - } - - island.RecalculateBounds(); - - islands.Add(island); - } - } - - // Loop through rest of triangles - return islands.ToArray(); - } - } -} diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs.meta b/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs.meta deleted file mode 100644 index b8c983f..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/MeshUtils.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: b9189791c85048a7ba79c1ad82a8f64b -timeCreated: 1659349711 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs b/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs deleted file mode 100644 index 11305ce..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// This script is originated from https://github.com/dgreenheck/OpenFracture - -using UnityEngine; - -namespace Gameplay.Fracture.Utilities -{ - public static class Vector3Extensions - { - // - // that the normal is pointing to - // - p: The point being checked - // - n: The normal of the plane - // - o: The origin of the plane - /// - /// Returns true if the point is either on or above the plane. "Above" is the side of the place in the direction of the normal. - /// - /// The test point - /// The plane normal - /// The plane origin - /// - public static bool IsAbovePlane(this Vector3 p, Vector3 n, Vector3 o) - { - return (n.x * (p.x - o.x) + n.y * (p.y - o.y) + n.z * (p.z - o.z)) >= 0; - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs.meta deleted file mode 100644 index 7e45e7c..0000000 --- a/Assets/Scripts/Gameplay/Fracture/Utilities/Vector3Extensions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 2331a5eec99f407eac0e79821bb56d04 -timeCreated: 1659349711 \ No newline at end of file From 8bb8ee8526c9b0e76111bee55d106f6c2fcb1b9a Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:24:10 +0800 Subject: [PATCH 03/10] feat: delegates event in ragdoll core --- .../Gameplay/Ragdoll/Core/RagdollAnimation.cs | 15 ++++++--- .../Gameplay/Ragdoll/Core/RagdollBody.cs | 9 ++++-- .../Gameplay/Ragdoll/Core/RagdollCamera.cs | 32 ++++++++++++++++--- .../Gameplay/Ragdoll/Core/RagdollGrab.cs | 12 ++++--- .../Gameplay/Ragdoll/Core/RagdollMovement.cs | 8 +++-- .../Gameplay/Ragdoll/Core/RagdollPhysics.cs | 12 ++++--- .../Gameplay/Ragdoll/Input/InputBase.cs | 18 ++++++++--- .../Gameplay/Ragdoll/Input/RagdollInputs.cs | 12 +++++-- .../Scripts/Gameplay/Ragdoll/RagdollCore.cs | 13 +++++++- 9 files changed, 99 insertions(+), 32 deletions(-) diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollAnimation.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollAnimation.cs index 25e79be..508e2af 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollAnimation.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollAnimation.cs @@ -50,12 +50,9 @@ public class RagdollAnimation : RagdollCore private float _targetDirVerticalPercent; - private void Start() + protected override void Start() { - - // input event delegates - ragdoll.inputs.OnLeftClickDelegates += UseLeftArm; - ragdoll.inputs.OnRightClickDelegates += UseRightArm; + base.Start(); // binding _joints = ragdoll.ragdollBody.physicalJoints; @@ -70,6 +67,14 @@ private void Start() } } + protected override void OnInputDelegate() + { + base.OnInputDelegate(); + // input event delegates + ragdoll.inputs.OnLeftClickDelegates += UseLeftArm; + ragdoll.inputs.OnRightClickDelegates += UseRightArm; + } + private void FixedUpdate() { UpdatePhysicalJoints(); diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollBody.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollBody.cs index 14cf6f6..c3dc43c 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollBody.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollBody.cs @@ -5,9 +5,12 @@ namespace Gameplay.Ragdoll.Core { - // The boyd and connections of the ragdoll - // the ragdoll has two bodies: animated one and physical one - // they bodies need to be synchronized in the runtime + /// The body and connections of the ragdoll + /// public class RagdollBody : RagdollCore { [Header("Body Torso")] diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollCamera.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollCamera.cs index 060a4e7..50527ea 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollCamera.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollCamera.cs @@ -51,8 +51,9 @@ public class RagdollCamera : RagdollCore public float minVerticalAngle = -20; // look down public float maxVerticalAngle = 60; // look up - public void Start() + protected override void Start() { + base.Start(); // create camera object in runtime if (cameraObject == null) { @@ -65,6 +66,15 @@ public void Start() _startDirection = lookAt.forward; } + + protected override void OnInputDelegate() + { + base.OnInputDelegate(); + ragdoll.inputs.OnLookDelegates += LookAroundInput; + ragdoll.inputs.OnScrollWheelDelegates += ScrollWheelInput; + } + + public void Update() { UpdateCamera(); @@ -122,17 +132,29 @@ private void AvoidObstacles() } // Player inputs - public void OnLook(InputValue value) + // no need to call delegates + // public void OnLook(InputValue value) + // { + // _inputDelta = value.Get() / 10; + // } + + private void LookAroundInput(Vector2 vector) { - _inputDelta = value.Get() / 10; + _inputDelta = vector / 10; } + private void ScrollWheelInput(Vector2 vector) + { + _currentDistance = Mathf.Clamp(_currentDistance + vector.y / 1200 * -scrollSensitivity, + minDistance, maxDistance); + } + + public void OnScrollWheel(InputValue value) { var scrollValue = value.Get(); - _currentDistance = Mathf.Clamp(_currentDistance + scrollValue.y / 1200 * -scrollSensitivity, - minDistance, maxDistance); } + } } diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs index 1610e7b..65f1792 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollGrab.cs @@ -13,8 +13,10 @@ public class RagdollGrab : RagdollCore private Hand _leftHand, _rightHand; - private void Start() + protected override void Start() { + base.Start(); + var leftHand = ragdoll.ragdollBody.GetPhysicalBone(HumanBodyBones.LeftHand).gameObject; var rightHand = ragdoll.ragdollBody.GetPhysicalBone(HumanBodyBones.RightHand).gameObject; @@ -26,20 +28,22 @@ private void Start() _leftHand.Init(ragdoll, jointMotionsConfig); _rightHand.Init(ragdoll, jointMotionsConfig); + } + + protected override void OnInputDelegate() + { + base.OnInputDelegate(); // input events binding ragdoll.inputs.OnLeftClickDelegates += GrabWithLeftHand; ragdoll.inputs.OnRightClickDelegates += GrabWithRightHand; } - public void GrabWithLeftHand(float weight) { - _leftHand.enabled = weight < leftHandBearWeight; _leftHand.enabled = weight < leftHandBearWeight && weight > 0.1f; } public void GrabWithRightHand(float weight) { - _rightHand.enabled = weight < rightHandBearWeight; _rightHand.enabled = weight < rightHandBearWeight && weight > 0.1f; } } diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollMovement.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollMovement.cs index ed0142f..3bcf581 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollMovement.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollMovement.cs @@ -19,19 +19,23 @@ public class RagdollMovement : RagdollCore private float raycastMaxDistance = 0.2f; private Rigidbody _footLeft, _footRight; - private void Start() + protected override void Start() { + base.Start(); // init the feet _footLeft = ragdoll.ragdollBody.GetPhysicalBone(HumanBodyBones.RightFoot).GetComponent(); _footRight = ragdoll.ragdollBody.GetPhysicalBone(HumanBodyBones.LeftFoot).GetComponent(); + } + protected override void OnInputDelegate() + { + base.OnInputDelegate(); // input events delegates ragdoll.inputs.OnMoveDelegates += MovementInput; ragdoll.inputs.OnJumpDelegates += JumpInput; ragdoll.inputs.OnGroundDelegates += UpdateRagdollOnGround; } - private void Update() { _aimAt = ragdoll.ragdollCamera.cameraObject.transform.forward; diff --git a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollPhysics.cs b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollPhysics.cs index 7d05e7d..593460b 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollPhysics.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Core/RagdollPhysics.cs @@ -71,17 +71,21 @@ public bool JumpState _jumping = value; } } - private void Start() + protected override void Start() { - // input delegates - ragdoll.inputs.OnMoveDelegates += ManualTorqueInput; - + base.Start(); // physics system starts UpdateTargetRotation(); InitStabilizer(); StartBalance(); } + protected override void OnInputDelegate() + { + base.OnInputDelegate(); + ragdoll.inputs.OnMoveDelegates += ManualTorqueInput; + } + // stabilizer is used to stable the ragdoll in upright position private void InitStabilizer() { diff --git a/Assets/Scripts/Gameplay/Ragdoll/Input/InputBase.cs b/Assets/Scripts/Gameplay/Ragdoll/Input/InputBase.cs index 6b7f624..efdc3e5 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Input/InputBase.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Input/InputBase.cs @@ -16,19 +16,27 @@ private void OnApplicationFocus(bool hasFocus) public delegate void OnLeftClickDelegate(float armWeight); public delegate void OnRightClickDelegate(float armWeight); public delegate void OnGroundDelegate(bool onFloor); // use to update the on-floor state + public delegate void OnLookDelegate(Vector2 look); + public delegate void OnScrollWheelDelegate(Vector2 scroll); // ------ Delegates ------ - public OnMoveDelegate OnMoveDelegates; - public OnJumpDelegate OnJumpDelegates; - public OnLeftClickDelegate OnLeftClickDelegates; - public OnRightClickDelegate OnRightClickDelegates; + public OnMoveDelegate OnMoveDelegates; + public OnJumpDelegate OnJumpDelegates; + public OnLeftClickDelegate OnLeftClickDelegates; + public OnRightClickDelegate OnRightClickDelegates; public OnGroundDelegate OnGroundDelegates; + public OnLookDelegate OnLookDelegates; + public OnScrollWheelDelegate OnScrollWheelDelegates; + // ------ Input System Events ------ public abstract void OnMove(InputValue value); public abstract void OnJump(InputValue value); public abstract void OnLeftClick(InputValue value); public abstract void OnRightClick(InputValue value); - + + public abstract void OnLook(InputValue value); + public abstract void OnScrollWheel(InputValue value); + } } \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Ragdoll/Input/RagdollInputs.cs b/Assets/Scripts/Gameplay/Ragdoll/Input/RagdollInputs.cs index 93542a8..9d0e7ce 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/Input/RagdollInputs.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/Input/RagdollInputs.cs @@ -26,15 +26,21 @@ public override void OnRightClick(InputValue value) { OnRightClickDelegates?.Invoke(value.Get()); } - - + public override void OnLook(InputValue value) + { + OnLookDelegates?.Invoke(value.Get()); + } + public override void OnScrollWheel(InputValue value) + { + OnScrollWheelDelegates?.Invoke(value.Get()); + } // ---------------------------------- private void OnApplicationFocus(bool hasFocus) { + // todo: gm can control this, pause menu... Cursor.lockState = hasFocus ? CursorLockMode.Locked : CursorLockMode.None; } - private void Start() { diff --git a/Assets/Scripts/Gameplay/Ragdoll/RagdollCore.cs b/Assets/Scripts/Gameplay/Ragdoll/RagdollCore.cs index bef7170..1d8791b 100644 --- a/Assets/Scripts/Gameplay/Ragdoll/RagdollCore.cs +++ b/Assets/Scripts/Gameplay/Ragdoll/RagdollCore.cs @@ -24,5 +24,16 @@ private void OnValidate() } } + protected virtual void Start() + { + // call to bind the inputs + OnInputDelegate(); + } + + /// + /// the input system events delegates binding + /// + protected virtual void OnInputDelegate() { } + } -} \ No newline at end of file +} From 028eb8a818c8331e98d6207a5a4dd0941a594a5a Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:24:50 +0800 Subject: [PATCH 04/10] fix: update my stickman --- Assets/Prefabs/Characters/MyStickman.prefab | 32 +- Assets/Scenes/Levels/Platform.unity | 508 +------------------- 2 files changed, 38 insertions(+), 502 deletions(-) diff --git a/Assets/Prefabs/Characters/MyStickman.prefab b/Assets/Prefabs/Characters/MyStickman.prefab index cd067d9..ed0cc9d 100644 --- a/Assets/Prefabs/Characters/MyStickman.prefab +++ b/Assets/Prefabs/Characters/MyStickman.prefab @@ -18,6 +18,7 @@ GameObject: - component: {fileID: 860747899} - component: {fileID: 860747892} - component: {fileID: 860747897} + - component: {fileID: 4557964835017376221} m_Layer: 8 m_Name: MyStickman m_TagString: Untagged @@ -60,6 +61,7 @@ MonoBehaviour: ragdollPhysics: {fileID: 860747895} ragdollMovement: {fileID: 860747896} ragdollCamera: {fileID: 860747899} + ragdollGrab: {fileID: 4557964835017376221} --- !u!114 &860747893 MonoBehaviour: m_ObjectHideFlags: 0 @@ -349,6 +351,10 @@ MonoBehaviour: cameraObject: {fileID: 0} smoothSpeed: 5 smooth: 1 + dontBlockCamera: + serializedVersion: 2 + m_Bits: 768 + cameraRepositionOffset: 0.15 improveSteepInclinations: 1 inclinationAngle: 30 inclinationDistance: 1.5 @@ -357,10 +363,6 @@ MonoBehaviour: initialDistance: 2 minVerticalAngle: -23 maxVerticalAngle: 60 - dontBlockCamera: - serializedVersion: 2 - m_Bits: 768 - cameraRepositionOffset: 0.15 --- !u!114 &860747892 MonoBehaviour: m_ObjectHideFlags: 0 @@ -404,6 +406,28 @@ MonoBehaviour: m_DefaultActionMap: Player m_SplitScreenIndex: -1 m_Camera: {fileID: 0} +--- !u!114 &4557964835017376221 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4551733872406489797} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3edbf242a2844ae7be4af74f23234afb, type: 3} + m_Name: + m_EditorClassIdentifier: + ragdoll: {fileID: 860747891} + leftHandBearWeight: 30 + rightHandBearWeight: 30 + jointMotionsConfig: + angularXMotion: 2 + angularYMotion: 2 + angularZMotion: 2 + angularXLimit: 30 + angularYLimit: 30 + angularZLimit: 30 --- !u!1001 &4551733871454457453 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/Levels/Platform.unity b/Assets/Scenes/Levels/Platform.unity index 1b57888..76316b5 100644 --- a/Assets/Scenes/Levels/Platform.unity +++ b/Assets/Scenes/Levels/Platform.unity @@ -197,18 +197,6 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 0f93d97ac712644429c206aa76833d5d, type: 3} ---- !u!4 &77047230 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 3006365400855981113, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!153 &77047231 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733872418819845, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &180654462 GameObject: m_ObjectHideFlags: 0 @@ -744,24 +732,6 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 9f3deac2160b418439ea330a7e476549, type: 3} ---- !u!4 &385886788 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 7108420293042584280, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!95 &400917859 stripped -Animator: - m_CorrespondingSourceObject: {fileID: 7944794490637169660, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!153 &443429887 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733873132799323, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &445774368 GameObject: m_ObjectHideFlags: 0 @@ -1304,12 +1274,6 @@ Mesh: offset: 0 size: 0 path: ---- !u!153 &620634820 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733872356072197, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1001 &689899825 PrefabInstance: m_ObjectHideFlags: 0 @@ -1379,12 +1343,6 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 77a7909e6debdbe4baf0f88f076c29a7, type: 3} ---- !u!153 &811629077 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871820952243, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1001 &856102268 PrefabInstance: m_ObjectHideFlags: 0 @@ -1454,347 +1412,6 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 4c1686a5131ac8b45bfe913f509289c0, type: 3} ---- !u!1 &860747889 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 4551733872406489797, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!114 &860747891 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3bb5245cd29e47889ea97714941a063d, type: 3} - m_Name: - m_EditorClassIdentifier: - inputs: {fileID: 860747892} - ragdollAnimation: {fileID: 860747893} - ragdollBody: {fileID: 860747894} - ragdollPhysics: {fileID: 860747895} - ragdollMovement: {fileID: 860747896} - ragdollCamera: {fileID: 860747899} ---- !u!114 &860747892 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d41f6993e6394d64980ba974d6d17f66, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!114 &860747893 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6d4de14da1d7479885d641cecbada4af, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - enableIK: 1 - minTargetDirAngle: -30 - maxTargetDirAngle: 60 - minArmsAngle: -70 - maxArmsAngle: 100 - minLookAngle: -50 - maxLookAngle: 60 - lookAngleOffset: 0 - armsAngleOffset: 0 - handsRotationOffset: 20 - armsHorizontalSeparation: 0.45 - armsDistance: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0.0083351135 - value: 0.79074097 - inSlope: -4.884974 - outSlope: -4.884974 - tangentMode: 34 - weightedMode: 0 - inWeight: 0 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 0.13333333 - value: 0.18012801 - inSlope: -1.9694839 - outSlope: -1.9694839 - tangentMode: 34 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 1 - value: 1 - inSlope: 0.9460062 - outSlope: 0.9460062 - tangentMode: 34 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 ---- !u!114 &860747894 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f5d0899785c94d10b1a561897181c806, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - animatedTorso: {fileID: 385886788} - physicalTorso: {fileID: 1645791404} - animatedAnimator: {fileID: 400917859} - physicalAnimator: {fileID: 1229992235} - bodyParts: - - name: Head Neck - joints: - - {fileID: 77047231} - - {fileID: 620634820} - strengthScale: 1 - - name: Torso - joints: - - {fileID: 1360756859} - strengthScale: 1 - - name: Left Arm - joints: - - {fileID: 1500272685} - - {fileID: 2002604404} - - {fileID: 811629077} - strengthScale: 1 - - name: Right Arm - joints: - - {fileID: 1700639830} - - {fileID: 1558071355} - - {fileID: 1594036011} - strengthScale: 1 - - name: Left Leg - joints: - - {fileID: 1871546982} - - {fileID: 955805659} - - {fileID: 1178394290} - strengthScale: 1 - - name: Right Leg - joints: - - {fileID: 443429887} - - {fileID: 2015752439} - - {fileID: 1629741870} - strengthScale: 1 - maxAngularVelocity: 50 - animatorHelper: {fileID: 0} ---- !u!114 &860747895 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9e409cd6a1b346bbbabc10aa11385d69, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - customTorsoAngularDrag: 0.05 - balanceMode: 2 - _stabilizerJointDriveConfig: - _positionSpring: 10000 - _positionDamper: 50 - _maximumForce: 800 - uprightTorque: 10000 - uprightTorqueFunction: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 2 - outSlope: 2 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.30369666 - value: 0.6073601 - inSlope: 1.10094 - outSlope: 1.10094 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 1 - value: 1 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - rotationTorque: 500 - manualTorque: 280 - maxManualRotSpeed: 3.5 - freezeRotationSpeed: 5 - jumpForce: 20000 ---- !u!114 &860747896 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: af71462dc9b24ab9be96acd26d3ef314, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - enable: 1 - speed: 2 - maxSlopeAngle: 60 ---- !u!114 &860747897 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 62899f850307741f2a39c98a8b639597, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Actions: {fileID: -944628639613478452, guid: f9a872b08583af94e96bbec862d26655, - type: 3} - m_NotificationBehavior: 0 - m_UIInputModule: {fileID: 0} - m_DeviceLostEvent: - m_PersistentCalls: - m_Calls: [] - m_DeviceRegainedEvent: - m_PersistentCalls: - m_Calls: [] - m_ControlsChangedEvent: - m_PersistentCalls: - m_Calls: [] - m_ActionEvents: [] - m_NeverAutoSwitchControlSchemes: 0 - m_DefaultControlScheme: - m_DefaultActionMap: Player - m_SplitScreenIndex: -1 - m_Camera: {fileID: 0} ---- !u!114 &860747898 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6d4de14da1d7479885d641cecbada4af, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - enableIK: 1 - minTargetDirAngle: -30 - maxTargetDirAngle: 60 - minArmsAngle: -70 - maxArmsAngle: 100 - minLookAngle: -50 - maxLookAngle: 60 - lookAngleOffset: 0 - armsAngleOffset: 0 - handsRotationOffset: 0 - armsHorizontalSeparation: 0.45 - armsDistance: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0.025001526 - value: 0.74450684 - inSlope: -4.4048233 - outSlope: -4.4048233 - tangentMode: 34 - weightedMode: 0 - inWeight: 0 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 0.14583333 - value: 0.21226412 - inSlope: -1.741298 - outSlope: -1.741298 - tangentMode: 34 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 1 - value: 1 - inSlope: 0.9222273 - outSlope: 0.9222273 - tangentMode: 34 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 ---- !u!114 &860747899 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 860747889} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d05560c1f5d34d9f9d0ca7328e1e1b94, type: 3} - m_Name: - m_EditorClassIdentifier: - ragdoll: {fileID: 860747891} - lookAt: {fileID: 77047230} - lookSensitivity: 0.5 - scrollSensitivity: 1 - invertX: 0 - invertY: 0 - cameraObject: {fileID: 0} - smoothSpeed: 5 - smooth: 1 - improveSteepInclinations: 1 - inclinationAngle: 30 - inclinationDistance: 1.5 - minDistance: 2 - maxDistance: 5 - initialDistance: 3.5 - minVerticalAngle: -30 - maxVerticalAngle: 60 - dontBlockCamera: - serializedVersion: 2 - m_Bits: 768 - cameraRepositionOffset: 0.15 --- !u!1 &921070632 stripped GameObject: m_CorrespondingSourceObject: {fileID: 6186655807582633324, guid: 0082833bc02dc544da92cd8e6e4a0743, @@ -1814,10 +1431,15 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: fractureOptions: - maxFractures: 50 - detectFloatingFragment: 1 - triggerType: 0 - minCollisionForce: 1 + fragmentCount: 10 + xAxis: 1 + yAxis: 1 + zAxis: 1 + detectFloatingFragments: 0 + asynchronous: 0 + insideMaterial: {fileID: 0} + textureScale: {x: 1, y: 1} + textureOffset: {x: 0, y: 0} callbackOptions: onCompleted: m_PersistentCalls: @@ -1920,12 +1542,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!153 &955805659 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733873424653649, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!850595691 &994926449 LightingSettings: m_ObjectHideFlags: 0 @@ -2386,7 +2002,6 @@ GameObject: - component: {fileID: 1148390486} - component: {fileID: 1148390485} - component: {fileID: 1148390484} - - component: {fileID: 1148390488} m_Layer: 0 m_Name: GrassCube m_TagString: Untagged @@ -2472,39 +2087,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 30 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1148390488 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1148390482} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: dcb81d28944043aaab3befb91e9428ca, type: 3} - m_Name: - m_EditorClassIdentifier: - fractureOptions: - maxFractures: 50 - detectFloatingFragment: 1 - triggerType: 0 - minCollisionForce: 1 - callbackOptions: - onCompleted: - m_PersistentCalls: - m_Calls: [] ---- !u!153 &1178394290 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733872325988808, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!95 &1229992235 stripped -Animator: - m_CorrespondingSourceObject: {fileID: 4925778503157553368, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &1318425278 GameObject: m_ObjectHideFlags: 0 @@ -2846,12 +2428,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 18 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!153 &1360756859 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871328887481, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &1398975427 GameObject: m_ObjectHideFlags: 0 @@ -3055,12 +2631,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 14 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!153 &1500272685 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871829148519, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &1502892291 GameObject: m_ObjectHideFlags: 0 @@ -3333,12 +2903,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 27 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!153 &1558071355 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871948155711, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!43 &1566421615 Mesh: m_ObjectHideFlags: 0 @@ -3503,18 +3067,6 @@ Mesh: offset: 0 size: 0 path: ---- !u!153 &1594036011 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733872275885467, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!153 &1629741870 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871660420916, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &1641821735 GameObject: m_ObjectHideFlags: 0 @@ -3546,12 +3098,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 35 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!54 &1645791404 stripped -Rigidbody: - m_CorrespondingSourceObject: {fileID: 4551733871879056946, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1001 &1687374470 PrefabInstance: m_ObjectHideFlags: 0 @@ -3644,7 +3190,7 @@ PrefabInstance: - target: {fileID: 8894856259471134342, guid: b21f208237dfa604184a809772254745, type: 3} propertyPath: m_IsActive - value: 1 + value: 0 objectReference: {fileID: 0} - target: {fileID: 8894856259471134342, guid: b21f208237dfa604184a809772254745, type: 3} @@ -3653,12 +3199,6 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: b21f208237dfa604184a809772254745, type: 3} ---- !u!153 &1700639830 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871785560313, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &1722860682 stripped GameObject: m_CorrespondingSourceObject: {fileID: 1315125114229348, guid: 4c1686a5131ac8b45bfe913f509289c0, @@ -3862,12 +3402,6 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1845180436} m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} ---- !u!153 &1871546982 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733871530681641, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!43 &1885208437 Mesh: m_ObjectHideFlags: 0 @@ -4509,18 +4043,6 @@ MonoBehaviour: m_RequiresDepthTexture: 0 m_RequiresColorTexture: 0 m_Version: 2 ---- !u!153 &2002604404 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733873418698430, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} ---- !u!153 &2015752439 stripped -ConfigurableJoint: - m_CorrespondingSourceObject: {fileID: 4551733873054704272, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - m_PrefabInstance: {fileID: 8542116741661447839} - m_PrefabAsset: {fileID: 0} --- !u!1 &2024690498 GameObject: m_ObjectHideFlags: 0 @@ -5305,11 +4827,6 @@ PrefabInstance: propertyPath: m_Name value: MyStickman objectReference: {fileID: 0} - - target: {fileID: 4551733872406489797, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - propertyPath: m_IsActive - value: 1 - objectReference: {fileID: 0} - target: {fileID: 4551733872406489798, guid: f16385ae5cc42664485bf041389d5b7b, type: 3} propertyPath: m_RootOrder @@ -5365,11 +4882,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 6189107397490735715, guid: f16385ae5cc42664485bf041389d5b7b, - type: 3} - propertyPath: m_Materials.Array.data[0] - value: - objectReference: {fileID: 2100000, guid: 95fcf9331ca635c459a201f2c56f44dc, type: 2} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: f16385ae5cc42664485bf041389d5b7b, type: 3} --- !u!1001 &8638334861115870275 From 0ee23bcfd23776d3a30b68e064fa08a14fb44927 Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:26:13 +0800 Subject: [PATCH 05/10] remove old testing code --- Assets/Scripts/Gameplay/DeprecatedChubby.meta | 3 - .../DeprecatedChubbyController.cs | 76 ----- .../DeprecatedChubbyController.cs.meta | 11 - .../DeprecatedChubby/DeprecatedCopyLimb.cs | 37 --- .../DeprecatedCopyLimb.cs.meta | 11 - .../DeprecatedChubby/InputSystem.meta | 8 - .../InputSystem/InputEvents.cs | 78 ----- .../InputSystem/InputEvents.cs.meta | 11 - .../InputSystem/MF1.inputsettings.asset | 35 --- .../InputSystem/MF1.inputsettings.asset.meta | 8 - .../PlayerInputAction.inputactions | 274 ------------------ .../PlayerInputAction.inputactions.meta | 14 - .../Gameplay/DeprecatedChubby/Movement.cs | 9 - .../DeprecatedChubby/Movement.cs.meta | 3 - 14 files changed, 578 deletions(-) delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs.meta diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby.meta b/Assets/Scripts/Gameplay/DeprecatedChubby.meta deleted file mode 100644 index 7a8f154..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 690ca2d78627450795fd78394f599ea2 -timeCreated: 1658456205 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs b/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs deleted file mode 100644 index f64df98..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using InputSystem; -using UnityEngine; - -namespace Chubby -{ - public class DeprecatedChubbyController : MonoBehaviour - { - [SerializeField] private float speed = 5f; - [SerializeField] private float jumpForce = 60f; - [SerializeField] private ConfigurableJoint hipJoint; - [SerializeField] private Rigidbody hip; - [SerializeField] private Animator targetAnimator; - private InputEvents _inputEvents; - private bool _isGrounded; - - private bool _walking; - - // Start is called before the first frame update - private void Start() - { - _inputEvents = GetComponent(); - } - - // Update is called once per frame - private void Update() - { - var horizontal = _inputEvents.move.x; - var vertical = _inputEvents.move.y; - var direction = new Vector3(horizontal, 0f, vertical).normalized; - - - // move - if (_inputEvents.move.magnitude >= 0.1f) // validation, 0f is not accurate - { - Move(direction); - } - else - { - _walking = false; - } - - // jump - if (_inputEvents.jump /* && isGrounded */) - { - Jump(); - } - - if (targetAnimator) targetAnimator.SetBool("Walk", _walking); - } - - private void GroundCheck() - { - // todo - _isGrounded = false; - } - - - private void Move(Vector3 direction) - { - var targetAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg; - - hipJoint.targetRotation = Quaternion.Euler(0f, targetAngle, 0f); - - hip.AddForce(direction * speed); - - _walking = true; - } - - private void Jump() - { - if (targetAnimator) targetAnimator.SetBool("Jump", true); - hip.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); - _inputEvents.jump = false; - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs.meta deleted file mode 100644 index 2463691..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedChubbyController.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d51ede52931a21d4c8a3e981df323d0e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs b/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs deleted file mode 100644 index 71acbde..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using Unity.Collections; -using UnityEngine; -using UnityEngine.Serialization; - -public class DeprecatedCopyLimb : MonoBehaviour -{ - [FormerlySerializedAs("targetLimb")] [SerializeField] - private Transform animatedLimb; - - private ConfigurableJoint _configurableJoint; - - private Quaternion _lastAnimatedRotation; - - void Start() - { - _configurableJoint = gameObject.GetComponent(); - _lastAnimatedRotation = animatedLimb.transform.localRotation; - } - - private void FixedUpdate() - { - SyncRotation(); - } - - /// - /// set physical limb rotation to match animated limb rotation. - /// the animated rotation should be cached in the Start() - /// - private void SyncRotation() - { - // todo: the position can be synced as well - Quaternion localRotation = animatedLimb.transform.localRotation; - _configurableJoint.targetRotation = Quaternion.Inverse(localRotation) * _lastAnimatedRotation; - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs.meta deleted file mode 100644 index cb94134..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/DeprecatedCopyLimb.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5f896e662cb8f19439396c39076aece4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem.meta deleted file mode 100644 index 5d31ac9..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 48c9acd554a28814b922162e3fa889b0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs deleted file mode 100644 index ada3011..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs +++ /dev/null @@ -1,78 +0,0 @@ -using UnityEngine; -using UnityEngine.InputSystem; - -namespace InputSystem -{ - public class InputEvents : MonoBehaviour - { - [Header("Character Input Values")] - public Vector2 move; - public Vector2 look; - public bool jump; - public bool sprint; - - [Header("Movement Settings")] - public bool analogMovement; - - [Header("Mouse Cursor Settings")] - public bool cursorLocked = true; - public bool cursorInputForLook = true; - -#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED - public void OnMove(InputValue value) - { - MoveInput(value.Get()); - } - - public void OnLook(InputValue value) - { - if(cursorInputForLook) - { - LookInput(value.Get()); - } - } - - public void OnJump(InputValue value) - { - JumpInput(value.isPressed); - } - - public void OnSprint(InputValue value) - { - SprintInput(value.isPressed); - } -#endif - - - public void MoveInput(Vector2 newMoveDirection) - { - move = newMoveDirection; - } - - public void LookInput(Vector2 newLookDirection) - { - look = newLookDirection; - } - - public void JumpInput(bool newJumpState) - { - jump = newJumpState; - } - - public void SprintInput(bool newSprintState) - { - sprint = !sprint; - } - - private void OnApplicationFocus(bool hasFocus) - { - SetCursorState(cursorLocked); - } - - private void SetCursorState(bool newState) - { - Cursor.lockState = newState ? CursorLockMode.Locked : CursorLockMode.None; - } - } - -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs.meta deleted file mode 100644 index 796f375..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/InputEvents.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e087ecce43ebbff45a1b360637807d93 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset deleted file mode 100644 index bb91482..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset +++ /dev/null @@ -1,35 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: c46f07b5ed07e4e92aa78254188d3d10, type: 3} - m_Name: MF1.inputsettings - m_EditorClassIdentifier: - m_SupportedDevices: [] - m_UpdateMode: 1 - m_MaxEventBytesPerUpdate: 5242880 - m_MaxQueuedEventsPerUpdate: 1000 - m_CompensateForScreenOrientation: 0 - m_BackgroundBehavior: 0 - m_EditorInputBehaviorInPlayMode: 0 - m_DefaultDeadzoneMin: 0.125 - m_DefaultDeadzoneMax: 0.925 - m_DefaultButtonPressPoint: 0.5 - m_ButtonReleaseThreshold: 0.75 - m_DefaultTapTime: 0.2 - m_DefaultSlowTapTime: 0.5 - m_DefaultHoldTime: 0.4 - m_TapRadius: 5 - m_MultiTapDelayTime: 0.75 - m_DisableRedundantEventsMerging: 0 - m_iOSSettings: - m_MotionUsage: - m_Enabled: 0 - m_Description: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset.meta deleted file mode 100644 index bb5f3dd..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/MF1.inputsettings.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9e7be553448fa2546aea5752021cbcf7 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions deleted file mode 100644 index c1cbf54..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions +++ /dev/null @@ -1,274 +0,0 @@ -{ - "name": "PlayerInputAction", - "maps": [ - { - "name": "Player", - "id": "f62a4b92-ef5e-4175-8f4c-c9075429d32c", - "actions": [ - { - "name": "Move", - "type": "Value", - "id": "6bc1aaf4-b110-4ff7-891e-5b9fe6f32c4d", - "expectedControlType": "Vector2", - "processors": "", - "interactions": "", - "initialStateCheck": true - }, - { - "name": "Look", - "type": "Value", - "id": "2690c379-f54d-45be-a724-414123833eb4", - "expectedControlType": "Vector2", - "processors": "", - "interactions": "", - "initialStateCheck": true - }, - { - "name": "Jump", - "type": "Button", - "id": "8c4abdf8-4099-493a-aa1a-129acec7c3df", - "expectedControlType": "Button", - "processors": "", - "interactions": "", - "initialStateCheck": false - }, - { - "name": "Sprint", - "type": "PassThrough", - "id": "980e881e-182c-404c-8cbf-3d09fdb48fef", - "expectedControlType": "", - "processors": "", - "interactions": "", - "initialStateCheck": false - } - ], - "bindings": [ - { - "name": "WASD", - "id": "b7594ddb-26c9-4ba2-bd5a-901468929edc", - "path": "2DVector(mode=1)", - "interactions": "", - "processors": "", - "groups": "", - "action": "Move", - "isComposite": true, - "isPartOfComposite": false - }, - { - "name": "up", - "id": "2063a8b5-6a45-43de-851b-65f3d46e7b58", - "path": "/w", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "down", - "id": "64e4d037-32e1-4fb9-80e4-fc7330404dfe", - "path": "/s", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "left", - "id": "0fce8b11-5eab-4e4e-a741-b732e7b20873", - "path": "/a", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "right", - "id": "7bdda0d6-57a8-47c8-8238-8aecf3110e47", - "path": "/d", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "up", - "id": "bb94b405-58d3-4998-8535-d705c1218a98", - "path": "/upArrow", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "down", - "id": "929d9071-7dd0-4368-9743-6793bb98087e", - "path": "/downArrow", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "left", - "id": "28abadba-06ff-4d37-bb70-af2f1e35a3b9", - "path": "/leftArrow", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "right", - "id": "45f115b6-9b4f-4ba8-b500-b94c93bf7d7e", - "path": "/rightArrow", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Move", - "isComposite": false, - "isPartOfComposite": true - }, - { - "name": "", - "id": "e2f9aa65-db06-4c5b-a2e9-41bc8acb9517", - "path": "/leftStick", - "interactions": "", - "processors": "StickDeadzone", - "groups": "Gamepad", - "action": "Move", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "ed66cbff-2900-4a62-8896-696503cfcd31", - "path": "/delta", - "interactions": "", - "processors": "InvertVector2(invertX=false),ScaleVector2(x=0.05,y=0.05)", - "groups": "KeyboardMouse", - "action": "Look", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "d1d171b6-19d8-47a6-ba3a-71b6a8e7b3c0", - "path": "/rightStick", - "interactions": "", - "processors": "InvertVector2(invertX=false),StickDeadzone,ScaleVector2(x=300,y=300)", - "groups": "Gamepad", - "action": "Look", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "1bd55a0b-761e-4ae4-89ae-8ec127e08a29", - "path": "/space", - "interactions": "", - "processors": "", - "groups": "KeyboardMouse", - "action": "Jump", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "9f973413-5e27-4239-acee-38c4a63feeba", - "path": "/buttonSouth", - "interactions": "", - "processors": "", - "groups": "Gamepad", - "action": "Jump", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "dc65b89f-9bd3-43fb-92af-d0d87ba5faa4", - "path": "/leftShift", - "interactions": "Press", - "processors": "", - "groups": "KeyboardMouse", - "action": "Sprint", - "isComposite": false, - "isPartOfComposite": false - }, - { - "name": "", - "id": "c8fcd86e-dcfd-4f88-8e93-b638cdbf3320", - "path": "/leftTrigger", - "interactions": "", - "processors": "", - "groups": "Gamepad", - "action": "Sprint", - "isComposite": false, - "isPartOfComposite": false - } - ] - } - ], - "controlSchemes": [ - { - "name": "KeyboardMouse", - "bindingGroup": "KeyboardMouse", - "devices": [ - { - "devicePath": "", - "isOptional": false, - "isOR": false - }, - { - "devicePath": "", - "isOptional": false, - "isOR": false - } - ] - }, - { - "name": "Gamepad", - "bindingGroup": "Gamepad", - "devices": [ - { - "devicePath": "", - "isOptional": true, - "isOR": false - }, - { - "devicePath": "", - "isOptional": true, - "isOR": false - }, - { - "devicePath": "", - "isOptional": true, - "isOR": false - } - ] - }, - { - "name": "Xbox Controller", - "bindingGroup": "Xbox Controller", - "devices": [] - }, - { - "name": "PS4 Controller", - "bindingGroup": "PS4 Controller", - "devices": [] - } - ] -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions.meta deleted file mode 100644 index 7d4fb54..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/InputSystem/PlayerInputAction.inputactions.meta +++ /dev/null @@ -1,14 +0,0 @@ -fileFormatVersion: 2 -guid: 4419d82f33d36e848b3ed5af4c8da37e -ScriptedImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 2 - userData: - assetBundleName: - assetBundleVariant: - script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} - generateWrapperCode: 0 - wrapperCodePath: - wrapperClassName: - wrapperCodeNamespace: diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs b/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs deleted file mode 100644 index de9695c..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs +++ /dev/null @@ -1,9 +0,0 @@ -using UnityEngine; - -namespace Chubby -{ - public class Movement : MonoBehaviour - { - - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs.meta b/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs.meta deleted file mode 100644 index 62c55e4..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedChubby/Movement.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: c1760deeabbf40cca3b7ce217cb03f77 -timeCreated: 1658456211 \ No newline at end of file From 3e63c6d7c37558562611b589785d01908b753b44 Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:27:36 +0800 Subject: [PATCH 06/10] clean up --- Assets/Scripts/Gameplay/{Active Ragdoll.meta => Old Ragdoll.meta} | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/ActiveRagdoll.cs | 0 .../{Active Ragdoll => Old Ragdoll}/ActiveRagdoll.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/DefaultBehaviour.cs | 0 .../{Active Ragdoll => Old Ragdoll}/DefaultBehaviour.cs.meta | 0 .../Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input.meta | 0 .../Input/ActiveRagdollActions.inputactions | 0 .../Input/ActiveRagdollActions.inputactions.meta | 0 .../Input/InputSystem.inputsettings.asset | 0 .../Input/InputSystem.inputsettings.asset.meta | 0 .../Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/AnimationModule.cs | 0 .../Modules/AnimationModule.cs.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/CameraModule.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/CameraModule.cs.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/GripModule.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/GripModule.cs.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/InputModule.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/InputModule.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/Module.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/Module.cs.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/PhysicsModule.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Modules/PhysicsModule.cs.meta | 0 .../Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others.meta | 0 .../{Active Ragdoll => Old Ragdoll}/Others/AnimatorHelper.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Others/AnimatorHelper.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Auxiliary.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Others/Auxiliary.cs.meta | 0 .../Others/ConfigurableJointExtensions.cs | 0 .../Others/ConfigurableJointExtensions.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Floor.cs | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Floor.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Grippable.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Others/Grippable.cs.meta | 0 .../Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Gripper.cs | 0 .../{Active Ragdoll => Old Ragdoll}/Others/Gripper.cs.meta | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename Assets/Scripts/Gameplay/{Active Ragdoll.meta => Old Ragdoll.meta} (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/ActiveRagdoll.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/ActiveRagdoll.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/DefaultBehaviour.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/DefaultBehaviour.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input/ActiveRagdollActions.inputactions (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input/ActiveRagdollActions.inputactions.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input/InputSystem.inputsettings.asset (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Input/InputSystem.inputsettings.asset.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/AnimationModule.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/AnimationModule.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/CameraModule.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/CameraModule.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/GripModule.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/GripModule.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/InputModule.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/InputModule.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/Module.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/Module.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/PhysicsModule.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Modules/PhysicsModule.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/AnimatorHelper.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/AnimatorHelper.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Auxiliary.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Auxiliary.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/ConfigurableJointExtensions.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/ConfigurableJointExtensions.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Floor.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Floor.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Grippable.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Grippable.cs.meta (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Gripper.cs (100%) rename Assets/Scripts/Gameplay/{Active Ragdoll => Old Ragdoll}/Others/Gripper.cs.meta (100%) diff --git a/Assets/Scripts/Gameplay/Active Ragdoll.meta b/Assets/Scripts/Gameplay/Old Ragdoll.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll.meta rename to Assets/Scripts/Gameplay/Old Ragdoll.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/ActiveRagdoll.cs b/Assets/Scripts/Gameplay/Old Ragdoll/ActiveRagdoll.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/ActiveRagdoll.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/ActiveRagdoll.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/ActiveRagdoll.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/ActiveRagdoll.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/ActiveRagdoll.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/ActiveRagdoll.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/DefaultBehaviour.cs b/Assets/Scripts/Gameplay/Old Ragdoll/DefaultBehaviour.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/DefaultBehaviour.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/DefaultBehaviour.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/DefaultBehaviour.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/DefaultBehaviour.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/DefaultBehaviour.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/DefaultBehaviour.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Input.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Input.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Input.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Input.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Input/ActiveRagdollActions.inputactions b/Assets/Scripts/Gameplay/Old Ragdoll/Input/ActiveRagdollActions.inputactions similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Input/ActiveRagdollActions.inputactions rename to Assets/Scripts/Gameplay/Old Ragdoll/Input/ActiveRagdollActions.inputactions diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Input/ActiveRagdollActions.inputactions.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Input/ActiveRagdollActions.inputactions.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Input/ActiveRagdollActions.inputactions.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Input/ActiveRagdollActions.inputactions.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Input/InputSystem.inputsettings.asset b/Assets/Scripts/Gameplay/Old Ragdoll/Input/InputSystem.inputsettings.asset similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Input/InputSystem.inputsettings.asset rename to Assets/Scripts/Gameplay/Old Ragdoll/Input/InputSystem.inputsettings.asset diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Input/InputSystem.inputsettings.asset.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Input/InputSystem.inputsettings.asset.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Input/InputSystem.inputsettings.asset.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Input/InputSystem.inputsettings.asset.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/AnimationModule.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/AnimationModule.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/AnimationModule.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/AnimationModule.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/AnimationModule.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/AnimationModule.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/AnimationModule.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/AnimationModule.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/CameraModule.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/CameraModule.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/CameraModule.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/CameraModule.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/CameraModule.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/CameraModule.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/CameraModule.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/CameraModule.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/GripModule.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/GripModule.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/GripModule.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/GripModule.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/GripModule.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/GripModule.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/GripModule.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/GripModule.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/InputModule.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/InputModule.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/InputModule.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/InputModule.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/InputModule.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/InputModule.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/InputModule.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/InputModule.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/Module.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/Module.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/Module.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/Module.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/Module.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/Module.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/Module.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/Module.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/PhysicsModule.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/PhysicsModule.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/PhysicsModule.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/PhysicsModule.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Modules/PhysicsModule.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Modules/PhysicsModule.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Modules/PhysicsModule.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Modules/PhysicsModule.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/AnimatorHelper.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/AnimatorHelper.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/AnimatorHelper.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/AnimatorHelper.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/AnimatorHelper.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/AnimatorHelper.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/AnimatorHelper.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/AnimatorHelper.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Auxiliary.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Auxiliary.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Auxiliary.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Auxiliary.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Auxiliary.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Auxiliary.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Auxiliary.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Auxiliary.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/ConfigurableJointExtensions.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/ConfigurableJointExtensions.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/ConfigurableJointExtensions.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/ConfigurableJointExtensions.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/ConfigurableJointExtensions.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/ConfigurableJointExtensions.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/ConfigurableJointExtensions.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/ConfigurableJointExtensions.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Floor.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Floor.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Floor.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Floor.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Floor.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Floor.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Floor.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Floor.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Grippable.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Grippable.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Grippable.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Grippable.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Grippable.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Grippable.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Grippable.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Grippable.cs.meta diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Gripper.cs b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Gripper.cs similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Gripper.cs rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Gripper.cs diff --git a/Assets/Scripts/Gameplay/Active Ragdoll/Others/Gripper.cs.meta b/Assets/Scripts/Gameplay/Old Ragdoll/Others/Gripper.cs.meta similarity index 100% rename from Assets/Scripts/Gameplay/Active Ragdoll/Others/Gripper.cs.meta rename to Assets/Scripts/Gameplay/Old Ragdoll/Others/Gripper.cs.meta From 67cdd392b83c68021167f80d21b82490277c892c Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:39:23 +0800 Subject: [PATCH 07/10] git error From 8691187228217270d9d34945e8820f073793d446 Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:42:25 +0800 Subject: [PATCH 08/10] rm old code --- .../Gameplay/DeprecatedBasicRigidBodyPush.cs | 35 -- .../DeprecatedBasicRigidBodyPush.cs.meta | 11 - .../Gameplay/DeprecatedPlayerController.cs | 393 ------------------ .../DeprecatedPlayerController.cs.meta | 11 - 4 files changed, 450 deletions(-) delete mode 100644 Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs.meta delete mode 100644 Assets/Scripts/Gameplay/DeprecatedPlayerController.cs delete mode 100644 Assets/Scripts/Gameplay/DeprecatedPlayerController.cs.meta diff --git a/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs b/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs deleted file mode 100644 index acbf20b..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs +++ /dev/null @@ -1,35 +0,0 @@ -using UnityEngine; - -public class DeprecatedBasicRigidBodyPush : MonoBehaviour -{ - public LayerMask pushLayers; - public bool canPush; - [Range(0.5f, 50f)] public float strength = 1.1f; - - private void OnControllerColliderHit(ControllerColliderHit hit) - { - if (canPush) PushRigidBodies(hit); - } - - private void PushRigidBodies(ControllerColliderHit hit) - { - // https://docs.unity3d.com/ScriptReference/CharacterController.OnControllerColliderHit.html - - // make sure we hit a non kinematic rigidbody - Rigidbody body = hit.collider.attachedRigidbody; - if (body == null || body.isKinematic) return; - - // make sure we only push desired layer(s) - var bodyLayerMask = 1 << body.gameObject.layer; - if ((bodyLayerMask & pushLayers.value) == 0) return; - - // We dont want to push objects below us - if (hit.moveDirection.y < -0.3f) return; - - // Calculate push direction from move direction, horizontal motion only - Vector3 pushDir = new Vector3(hit.moveDirection.x, 0.0f, hit.moveDirection.z); - - // Apply the push and take strength into account - body.AddForce(pushDir * strength, ForceMode.Impulse); - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs.meta b/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs.meta deleted file mode 100644 index d0dc3af..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedBasicRigidBodyPush.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 909d917d73a63f940ac158d02e936645 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs b/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs deleted file mode 100644 index 1778b47..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs +++ /dev/null @@ -1,393 +0,0 @@ -using InputSystem; -using UnityEngine; -using UnityEngine.InputSystem; - -/* Note: animations are called via the controller for both the character and capsule using animator null checks - */ - -namespace Gameplay -{ - [RequireComponent(typeof(CharacterController))] -#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED - [RequireComponent(typeof(PlayerInput))] -#endif - public class DeprecatedPlayerController : MonoBehaviour - { - [Header("Player")] - [Tooltip("Move speed of the character in m/s")] - public float MoveSpeed = 2.0f; - - [Tooltip("Sprint speed of the character in m/s")] - public float SprintSpeed = 5.335f; - - [Tooltip("How fast the character turns to face movement direction")] - [Range(0.0f, 0.3f)] - public float RotationSmoothTime = 0.12f; - - [Tooltip("Acceleration and deceleration")] - public float SpeedChangeRate = 10.0f; - - public AudioClip LandingAudioClip; - public AudioClip[] FootstepAudioClips; - [Range(0, 1)] public float FootstepAudioVolume = 0.5f; - - [Space(10)] - [Tooltip("The height the player can jump")] - public float JumpHeight = 1.2f; - - [Tooltip("The character uses its own gravity value. The engine default is -9.81f")] - public float Gravity = -15.0f; - - [Space(10)] - [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")] - public float JumpTimeout = 0.50f; - - [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")] - public float FallTimeout = 0.15f; - - [Header("Player Grounded")] - [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")] - public bool Grounded = true; - - [Tooltip("Useful for rough ground")] - public float GroundedOffset = -0.14f; - - [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")] - public float GroundedRadius = 0.28f; - - [Tooltip("What layers the character uses as ground")] - public LayerMask GroundLayers; - - [Header("Cinemachine")] - [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")] - public GameObject CinemachineCameraTarget; - - [Tooltip("How far in degrees can you move the camera up")] - public float TopClamp = 70.0f; - - [Tooltip("How far in degrees can you move the camera down")] - public float BottomClamp = -30.0f; - - [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")] - public float CameraAngleOverride = 0.0f; - - [Tooltip("For locking the camera position on all axis")] - public bool LockCameraPosition = false; - - // cinemachine - private float _cinemachineTargetYaw; - private float _cinemachineTargetPitch; - - // player - private float _speed; - private float _animationBlend; - private float _targetRotation = 0.0f; - private float _rotationVelocity; - private float _verticalVelocity; - private float _terminalVelocity = 53.0f; - - // timeout deltatime - private float _jumpTimeoutDelta; - private float _fallTimeoutDelta; - - // animation IDs - private int _animIDSpeed; - private int _animIDGrounded; - private int _animIDJump; - private int _animIDFreeFall; - private int _animIDMotionSpeed; - -#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED - private PlayerInput _playerInput; -#endif - private Animator _animator; - private CharacterController _controller; - private InputEvents _input; - private GameObject _mainCamera; - - private const float _threshold = 0.01f; - - private bool _hasAnimator; - - private bool IsCurrentDeviceMouse - { - get - { -#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED - return _playerInput.currentControlScheme == "KeyboardMouse"; -#else - return false; -#endif - } - } - - - private void Awake() - { - // get a reference to our main camera - if (_mainCamera == null) - { - _mainCamera = GameObject.FindGameObjectWithTag("MainCamera"); - } - } - - private void Start() - { - _cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y; - - _hasAnimator = TryGetComponent(out _animator); - _controller = GetComponent(); - _input = GetComponent(); -#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED - _playerInput = GetComponent(); -#else - Debug.LogError( "Starter Assets package is missing dependencies. Please use Tools/Starter Assets/Reinstall Dependencies to fix it"); -#endif - - AssignAnimationIDs(); - - // reset our timeouts on start - _jumpTimeoutDelta = JumpTimeout; - _fallTimeoutDelta = FallTimeout; - } - - private void Update() - { - _hasAnimator = TryGetComponent(out _animator); - - // check all inputs every frame - JumpAndGravity(); - GroundedCheck(); - Move(); - } - - private void LateUpdate() - { - CameraRotation(); - } - - private void AssignAnimationIDs() - { - _animIDSpeed = Animator.StringToHash("Speed"); - _animIDGrounded = Animator.StringToHash("Grounded"); - _animIDJump = Animator.StringToHash("Jump"); - _animIDFreeFall = Animator.StringToHash("FreeFall"); - _animIDMotionSpeed = Animator.StringToHash("MotionSpeed"); - } - - private void GroundedCheck() - { - // set sphere position, with offset - var position = transform.position; - Vector3 spherePosition = new Vector3(position.x, position.y - GroundedOffset, - position.z); - Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, - QueryTriggerInteraction.Ignore); - - // update animator if using character - if (_hasAnimator) - { - _animator.SetBool(_animIDGrounded, Grounded); - } - } - - private void CameraRotation() - { - // if there is an input and camera position is not fixed - if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition) - { - //Don't multiply mouse input by Time.deltaTime; - float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime; - - _cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier; - _cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier; - } - - // clamp our rotations so our values are limited 360 degrees - _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue); - _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp); - - // Cinemachine will follow this target - CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, - _cinemachineTargetYaw, 0.0f); - } - - private void Move() - { - // set target speed based on move speed, sprint speed and if sprint is pressed - float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed; - - // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon - - // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude - // if there is no input, set the target speed to 0 - if (_input.move == Vector2.zero) targetSpeed = 0.0f; - - // a reference to the players current horizontal velocity - float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude; - - float speedOffset = 0.1f; - float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f; - - // accelerate or decelerate to target speed - if (currentHorizontalSpeed < targetSpeed - speedOffset || - currentHorizontalSpeed > targetSpeed + speedOffset) - { - // creates curved result rather than a linear one giving a more organic speed change - // note T in Lerp is clamped, so we don't need to clamp our speed - _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, - Time.deltaTime * SpeedChangeRate); - - // round speed to 3 decimal places - _speed = Mathf.Round(_speed * 1000f) / 1000f; - } - else - { - _speed = targetSpeed; - } - - _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate); - if (_animationBlend < 0.01f) _animationBlend = 0f; - - // normalise input direction - Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized; - - // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude - // if there is a move input rotate player when the player is moving - if (_input.move != Vector2.zero) - { - _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + - _mainCamera.transform.eulerAngles.y; - float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity, - RotationSmoothTime); - - // rotate to face input direction relative to camera position - transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f); - } - - - Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward; - - // move the player - _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) + - new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime); - - // update animator if using character - if (_hasAnimator) - { - _animator.SetFloat(_animIDSpeed, _animationBlend); - _animator.SetFloat(_animIDMotionSpeed, inputMagnitude); - } - } - - private void JumpAndGravity() - { - if (Grounded) - { - // reset the fall timeout timer - _fallTimeoutDelta = FallTimeout; - - // update animator if using character - if (_hasAnimator) - { - _animator.SetBool(_animIDJump, false); - _animator.SetBool(_animIDFreeFall, false); - } - - // stop our velocity dropping infinitely when grounded - if (_verticalVelocity < 0.0f) - { - _verticalVelocity = -2f; - } - - // Jump - if (_input.jump && _jumpTimeoutDelta <= 0.0f) - { - // the square root of H * -2 * G = how much velocity needed to reach desired height - _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity); - - // update animator if using character - if (_hasAnimator) - { - _animator.SetBool(_animIDJump, true); - } - } - - // jump timeout - if (_jumpTimeoutDelta >= 0.0f) - { - _jumpTimeoutDelta -= Time.deltaTime; - } - } - else - { - // reset the jump timeout timer - _jumpTimeoutDelta = JumpTimeout; - - // fall timeout - if (_fallTimeoutDelta >= 0.0f) - { - _fallTimeoutDelta -= Time.deltaTime; - } - else - { - // update animator if using character - if (_hasAnimator) - { - _animator.SetBool(_animIDFreeFall, true); - } - } - - // if we are not grounded, do not jump - _input.jump = false; - } - - // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time) - if (_verticalVelocity < _terminalVelocity) - { - _verticalVelocity += Gravity * Time.deltaTime; - } - } - - private static float ClampAngle(float lfAngle, float lfMin, float lfMax) - { - if (lfAngle < -360f) lfAngle += 360f; - if (lfAngle > 360f) lfAngle -= 360f; - return Mathf.Clamp(lfAngle, lfMin, lfMax); - } - - private void OnDrawGizmosSelected() - { - Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f); - Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f); - - Gizmos.color = Grounded ? transparentGreen : transparentRed; - - // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider - var position = transform.position; - Gizmos.DrawSphere( - new Vector3(position.x, position.y - GroundedOffset, position.z), - GroundedRadius); - } - - private void OnFootstep(AnimationEvent animationEvent) - { - if (animationEvent.animatorClipInfo.weight > 0.5f) - { - if (FootstepAudioClips.Length > 0) - { - var index = Random.Range(0, FootstepAudioClips.Length); - AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume); - } - } - } - - private void OnLand(AnimationEvent animationEvent) - { - if (animationEvent.animatorClipInfo.weight > 0.5f) - { - AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume); - } - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs.meta b/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs.meta deleted file mode 100644 index 8c670a3..0000000 --- a/Assets/Scripts/Gameplay/DeprecatedPlayerController.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 26e54e5a728a9234ab24fcf1460ed8a2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From c7cc1f0afd9aa9de8ac5ff3b5627e70bb8404066 Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:49:10 +0800 Subject: [PATCH 09/10] feat: add my open fracture --- Assets/Scripts/Gameplay/Fracture.meta | 8 + Assets/Scripts/Gameplay/Fracture/Editor.meta | 8 + .../Editor/CallbackOptionsPropertyDrawer.cs | 25 + .../CallbackOptionsPropertyDrawer.cs.meta | 11 + .../Fracture/Editor/FractureEditor.cs | 8 + .../Fracture/Editor/FractureEditor.cs.meta | 11 + .../Editor/FractureOptionsPropertyDrawer.cs | 38 + .../FractureOptionsPropertyDrawer.cs.meta | 11 + .../Editor/OpenFracture.Editor.asmdef | 18 + .../Editor/OpenFracture.Editor.asmdef.meta | 7 + .../Fracture/Editor/PrefractureEditor.cs | 8 + .../Fracture/Editor/PrefractureEditor.cs.meta | 11 + .../PrefractureOptionsPropertyDrawer.cs | 63 ++ .../PrefractureOptionsPropertyDrawer.cs.meta | 11 + .../Editor/RefractureOptionsPropertyDrawer.cs | 35 + .../RefractureOptionsPropertyDrawer.cs.meta | 11 + .../Gameplay/Fracture/Editor/SliceEditor.cs | 8 + .../Fracture/Editor/SliceEditor.cs.meta | 11 + .../Editor/TriggerOptionsPropertyDrawer.cs | 57 ++ .../TriggerOptionsPropertyDrawer.cs.meta | 11 + .../Fracture/Editor/UnfreezeFragmentEditor.cs | 8 + .../Editor/UnfreezeFragmentEditor.cs.meta | 11 + Assets/Scripts/Gameplay/Fracture/Runtime.meta | 8 + .../Gameplay/Fracture/Runtime/Audio.meta | 8 + .../Fracture/Runtime/Audio/AudioMixer.mixer | 65 ++ .../Runtime/Audio/AudioMixer.mixer.meta | 8 + .../Fracture/Runtime/Audio/GlassBreaking.mp3 | Bin 0 -> 87057 bytes .../Runtime/Audio/GlassBreaking.mp3.meta | 22 + .../Fracture/Runtime/Audio/Slicing.mp3 | Bin 0 -> 5383 bytes .../Fracture/Runtime/Audio/Slicing.mp3.meta | 22 + .../Fracture/Runtime/Audio/StoneBreaking.mp3 | Bin 0 -> 9027 bytes .../Runtime/Audio/StoneBreaking.mp3.meta | 22 + .../Gameplay/Fracture/Runtime/Scripts.meta | 8 + .../Fracture/Runtime/Scripts/AssemblyInfo.cs | 4 + .../Runtime/Scripts/AssemblyInfo.cs.meta | 11 + .../Fracture/Runtime/Scripts/Fracture.cs | 257 ++++++ .../Fracture/Runtime/Scripts/Fracture.cs.meta | 11 + .../Fracture/Runtime/Scripts/Fragment.meta | 8 + .../Fragment/ConstrainedTriangulator.cs | 764 ++++++++++++++++++ .../Fragment/ConstrainedTriangulator.cs.meta | 11 + .../Scripts/Fragment/EdgeConstraint.cs | 87 ++ .../Scripts/Fragment/EdgeConstraint.cs.meta | 11 + .../Runtime/Scripts/Fragment/FragmentData.cs | 317 ++++++++ .../Scripts/Fragment/FragmentData.cs.meta | 11 + .../Runtime/Scripts/Fragment/Fragmenter.cs | 300 +++++++ .../Scripts/Fragment/Fragmenter.cs.meta | 11 + .../Runtime/Scripts/Fragment/MeshSlicer.cs | 320 ++++++++ .../Scripts/Fragment/MeshSlicer.cs.meta | 11 + .../Runtime/Scripts/Fragment/MeshVertex.cs | 54 ++ .../Scripts/Fragment/MeshVertex.cs.meta | 11 + .../Fracture/Runtime/Scripts/Fragment/Quad.cs | 56 ++ .../Runtime/Scripts/Fragment/Quad.cs.meta | 11 + .../Scripts/Fragment/TriangulationPoint.cs | 40 + .../Fragment/TriangulationPoint.cs.meta | 11 + .../Runtime/Scripts/Fragment/Triangulator.cs | 599 ++++++++++++++ .../Scripts/Fragment/Triangulator.cs.meta | 11 + .../Scripts/Fragment/UnfreezeFragment.cs | 83 ++ .../Scripts/Fragment/UnfreezeFragment.cs.meta | 11 + .../Scripts/OpenFracture.Runtime.asmdef | 14 + .../Scripts/OpenFracture.Runtime.asmdef.meta | 7 + .../Fracture/Runtime/Scripts/Options.meta | 8 + .../Scripts/Options/CallbackOptions.cs | 15 + .../Scripts/Options/CallbackOptions.cs.meta | 11 + .../Scripts/Options/FractureOptions.cs | 50 ++ .../Scripts/Options/FractureOptions.cs.meta | 11 + .../Scripts/Options/PrefractureOptions.cs | 25 + .../Options/PrefractureOptions.cs.meta | 11 + .../Scripts/Options/RefractureOptions.cs | 27 + .../Scripts/Options/RefractureOptions.cs.meta | 11 + .../Runtime/Scripts/Options/SliceOptions.cs | 39 + .../Scripts/Options/SliceOptions.cs.meta | 11 + .../Runtime/Scripts/Options/TriggerOptions.cs | 48 ++ .../Scripts/Options/TriggerOptions.cs.meta | 11 + .../Fracture/Runtime/Scripts/Prefracture.cs | 130 +++ .../Runtime/Scripts/Prefracture.cs.meta | 11 + .../Fracture/Runtime/Scripts/Slice.cs | 131 +++ .../Fracture/Runtime/Scripts/Slice.cs.meta | 11 + .../Fracture/Runtime/Scripts/Slicers.meta | 8 + .../Runtime/Scripts/Slicers/PlaneSlicer.cs | 84 ++ .../Scripts/Slicers/PlaneSlicer.cs.meta | 11 + .../Fracture/Runtime/Scripts/Support.meta | 8 + .../Scripts/Support/CameraController.cs | 77 ++ .../Scripts/Support/CameraController.cs.meta | 11 + .../Runtime/Scripts/Support/Projectile.cs | 26 + .../Scripts/Support/Projectile.cs.meta | 11 + .../Runtime/Scripts/Support/ToggleText.cs | 17 + .../Scripts/Support/ToggleText.cs.meta | 11 + .../Runtime/Scripts/Support/UniqueMaterial.cs | 13 + .../Scripts/Support/UniqueMaterial.cs.meta | 11 + .../Fracture/Runtime/Scripts/Utilities.meta | 8 + .../Runtime/Scripts/Utilities/BinSort.cs | 96 +++ .../Runtime/Scripts/Utilities/BinSort.cs.meta | 11 + .../Runtime/Scripts/Utilities/MathUtils.cs | 128 +++ .../Scripts/Utilities/MathUtils.cs.meta | 11 + .../Runtime/Scripts/Utilities/MeshUtils.cs | 239 ++++++ .../Scripts/Utilities/MeshUtils.cs.meta | 11 + .../Scripts/Utilities/Vector3Extensions.cs | 22 + .../Utilities/Vector3Extensions.cs.meta | 11 + 98 files changed, 4981 insertions(+) create mode 100644 Assets/Scripts/Gameplay/Fracture.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3 create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3 create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/StoneBreaking.mp3 create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Audio/StoneBreaking.mp3.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/AssemblyInfo.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/AssemblyInfo.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fracture.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fracture.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs.meta create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs create mode 100644 Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs.meta diff --git a/Assets/Scripts/Gameplay/Fracture.meta b/Assets/Scripts/Gameplay/Fracture.meta new file mode 100644 index 0000000..681afef --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b9afaa70aedb224a81faec990d79e81 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor.meta b/Assets/Scripts/Gameplay/Fracture/Editor.meta new file mode 100644 index 0000000..93b520a --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8028e5705acb01c4c9a053b2bd52227d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs b/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs new file mode 100644 index 0000000..da4aff2 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs @@ -0,0 +1,25 @@ +using UnityEditor; +using UnityEngine; + +[CustomPropertyDrawer(typeof(CallbackOptions))] +public class CallbackOptionsPropertyDrawer : PropertyDrawer +{ + private static bool foldout = true; + + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, label); + + if (foldout) + { + EditorGUILayout.PropertyField(property.FindPropertyRelative("onCompleted")); + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + } + + // Hack to prevent extra space at top of property drawer. This is due to using EditorGUILayout + // in OnGUI, but I don't want to have to manually specify control sizes + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs.meta new file mode 100644 index 0000000..b8346a7 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/CallbackOptionsPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e56ee0315133e004e8ef0f70106090ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs b/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs new file mode 100644 index 0000000..304f471 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs @@ -0,0 +1,8 @@ +using UnityEditor; + +[CustomEditor(typeof(Fracture))] +[CanEditMultipleObjects] +public class FractureEditor : Editor +{ + // Empty editor required for custom property drawers to work properly +} diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs.meta new file mode 100644 index 0000000..afc0768 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/FractureEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69f5d525b9339e54d97390fa221d2097 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs b/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs new file mode 100644 index 0000000..7ac455e --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs @@ -0,0 +1,38 @@ +using UnityEditor; +using UnityEngine; + +// IngredientDrawerUIE +[CustomPropertyDrawer(typeof(FractureOptions))] +public class FractureOptionsPropertyDrawer : PropertyDrawer +{ + private static bool foldout = true; + + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.indentLevel = 0; + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, label); + + if (foldout) + { + EditorGUI.indentLevel = 1; + + EditorGUILayout.PropertyField(property.FindPropertyRelative("fragmentCount")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("asynchronous")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("detectFloatingFragments")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("xAxis"), new GUIContent("Fracture Along X Plane")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("yAxis"), new GUIContent("Fracture Along Y Plane")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("zAxis"), new GUIContent("Fracture Along Z Plane")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("insideMaterial")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("textureScale")); + EditorGUILayout.PropertyField(property.FindPropertyRelative("textureOffset")); + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUI.indentLevel = 0; + } + + // Hack to prevent extra space at top of property drawer. This is due to using EditorGUILayout + // in OnGUI, but I don't want to have to manually specify control sizes + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs.meta new file mode 100644 index 0000000..f72638c --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/FractureOptionsPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6dc10c5bce663b74da7236629b70b065 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef b/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef new file mode 100644 index 0000000..9d7b8bf --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "OpenFracture.Editor", + "rootNamespace": "", + "references": [ + "GUID:7b1a8d9f4355a214f9a95471fa510c86" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef.meta b/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef.meta new file mode 100644 index 0000000..1bf3df1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/OpenFracture.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 948c819856827144ba8a7bba3f8946df +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs new file mode 100644 index 0000000..fd075c8 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs @@ -0,0 +1,8 @@ +using UnityEditor; + +[CustomEditor(typeof(Prefracture))] +[CanEditMultipleObjects] +public class PrefractureEditor : Editor +{ + // Empty editor required for custom property drawers to work properly +} diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs.meta new file mode 100644 index 0000000..fd10a2d --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b13c64051dd526c4f8906d68712e987b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs new file mode 100644 index 0000000..e4c7664 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs @@ -0,0 +1,63 @@ +using UnityEditor; +using UnityEngine; + +[CustomPropertyDrawer(typeof(PrefractureOptions))] +public class PrefractureOptionsPropertyDrawer : PropertyDrawer +{ + private static bool foldout = true; + + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var unfreezeAll = property.FindPropertyRelative("unfreezeAll"); + var saveFragmentsToDisk = property.FindPropertyRelative("saveFragmentsToDisk"); + var saveLocation = property.FindPropertyRelative("saveLocation"); + + EditorGUI.indentLevel = 0; + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, label); + + if (foldout) + { + EditorGUI.indentLevel = 1; + + EditorGUILayout.PropertyField(unfreezeAll, new GUIContent("Unfreeze All")); + EditorGUILayout.PropertyField(saveFragmentsToDisk, new GUIContent("Save Fragments To Disk")); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PropertyField(saveLocation, new GUIContent("Save Location")); + + if (GUILayout.Button(" . . . ", GUILayout.ExpandWidth(false))) + { + string path = EditorUtility.OpenFolderPanel("Select Save Location", "", ""); + if (path.StartsWith(Application.dataPath)) + { + saveLocation.stringValue = "Assets" + path.Substring(Application.dataPath.Length); + saveLocation.serializedObject.ApplyModifiedProperties(); + } + else + { + throw new System.ArgumentException("Full path does not contain the current project's Assets folder", "absolutePath"); + } + } + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(4); + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(16); + if (GUILayout.Button("Prefracture Mesh", new GUILayoutOption[] { GUILayout.ExpandWidth(true), GUILayout.Height(32) })) + { + ((Prefracture)property.serializedObject.targetObject).ComputeFracture(); + } + GUILayout.Space(16); + EditorGUILayout.EndHorizontal(); + GUILayout.Space(4); + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUI.indentLevel = 0; + } + + // Hack to prevent extra space at top of property drawer. This is due to using EditorGUILayout + // in OnGUI, but I don't want to have to manually specify control sizes + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs.meta new file mode 100644 index 0000000..14e6e33 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/PrefractureOptionsPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d08b34658d6e1ea4d83a8079e48bb270 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs b/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs new file mode 100644 index 0000000..02ccff2 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using UnityEngine; + +// IngredientDrawerUIE +[CustomPropertyDrawer(typeof(RefractureOptions))] +public class RefractureOptionsPropertyDrawer : PropertyDrawer +{ + private static bool foldout = true; + + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var enableRefracturing = property.FindPropertyRelative("enableRefracturing"); + var maxRefractureCount = property.FindPropertyRelative("maxRefractureCount"); + var invokeCallbacks = property.FindPropertyRelative("invokeCallbacks"); + + EditorGUI.indentLevel = 0; + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, label); + + if (foldout) + { + EditorGUI.indentLevel = 1; + EditorGUILayout.PropertyField(enableRefracturing, new GUIContent("Enabled")); + EditorGUILayout.PropertyField(invokeCallbacks, new GUIContent("Invoke Callbacks")); + EditorGUILayout.PropertyField(maxRefractureCount, new GUIContent("Max # of Refractures")); + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUI.indentLevel = 0; + } + + // Hack to prevent extra space at top of property drawer. This is due to using EditorGUILayout + // in OnGUI, but I don't want to have to manually specify control sizes + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs.meta new file mode 100644 index 0000000..20d67ee --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/RefractureOptionsPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d99dbaf6b5735b4c9af0896040ac65b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs b/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs new file mode 100644 index 0000000..381d3af --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs @@ -0,0 +1,8 @@ +using UnityEditor; + +[CustomEditor(typeof(Slice))] +[CanEditMultipleObjects] +public class SliceEditor : Editor +{ + // Empty editor required for custom property drawers to work properly +} diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs.meta new file mode 100644 index 0000000..f8bdd5f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/SliceEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f692bceb73d91e24681b607ab09f2ee6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs b/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs new file mode 100644 index 0000000..444e78f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs @@ -0,0 +1,57 @@ +using UnityEditor; +using UnityEngine; + +// IngredientDrawerUIE +[CustomPropertyDrawer(typeof(TriggerOptions))] +public class TriggerOptionsPropertyDrawer : PropertyDrawer +{ + private static bool foldout = true; + + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + // Create property container element. + var minimumCollisionForce = property.FindPropertyRelative("minimumCollisionForce"); + var triggerType = property.FindPropertyRelative("triggerType"); + var triggerKey = property.FindPropertyRelative("triggerKey"); + var filterCollisionsByTag = property.FindPropertyRelative("filterCollisionsByTag"); + var triggerAllowedTags = property.FindPropertyRelative("triggerAllowedTags"); + + EditorGUI.indentLevel = 0; + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, label); + + if (foldout) + { + EditorGUI.indentLevel = 1; + EditorGUILayout.PropertyField(triggerType); + + switch (triggerType.enumValueIndex) + { + case ((int)TriggerType.Collision): + EditorGUILayout.PropertyField(minimumCollisionForce); + EditorGUILayout.PropertyField(filterCollisionsByTag, new GUIContent("Limit collisions to selected tags?")); + if (filterCollisionsByTag.boolValue) + { + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUILayout.PropertyField(triggerAllowedTags, new GUIContent("Included Tags")); + } + break; + case ((int)TriggerType.Trigger): + EditorGUILayout.PropertyField(filterCollisionsByTag); + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUILayout.PropertyField(triggerAllowedTags); + break; + case ((int)TriggerType.Keyboard): + EditorGUILayout.PropertyField(triggerKey); + break; + } + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + EditorGUI.indentLevel = 0; + } + + // Hack to prevent extra space at top of property drawer. This is due to using EditorGUILayout + // in OnGUI, but I don't want to have to manually specify control sizes + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return 0; } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs.meta new file mode 100644 index 0000000..b1e06cb --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/TriggerOptionsPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e2055e9f36d75c4e9cf52e3380fa95b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs b/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs new file mode 100644 index 0000000..5ad7dc4 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs @@ -0,0 +1,8 @@ +using UnityEditor; + +[CustomEditor(typeof(UnfreezeFragment))] +[CanEditMultipleObjects] +public class UnfreezeFragmentEditor : Editor +{ + // Empty editor required for custom property drawers to work properly +} diff --git a/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs.meta b/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs.meta new file mode 100644 index 0000000..ed8b945 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Editor/UnfreezeFragmentEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acbae3f2ee815e34c8322878fe2df558 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime.meta b/Assets/Scripts/Gameplay/Fracture/Runtime.meta new file mode 100644 index 0000000..f8bd8e2 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2690d5679e5dc50469ccc1ce019094be +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio.meta new file mode 100644 index 0000000..506f140 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74d35284bc1d52a41b7220d8151cda46 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer new file mode 100644 index 0000000..5bcd307 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer @@ -0,0 +1,65 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!241 &24100000 +AudioMixerController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AudioMixer + m_OutputGroup: {fileID: 0} + m_MasterGroup: {fileID: 24300002} + m_Snapshots: + - {fileID: 24500006} + m_StartSnapshot: {fileID: 24500006} + m_SuspendThreshold: -80 + m_EnableSuspend: 1 + m_UpdateMode: 0 + m_ExposedParameters: [] + m_AudioMixerGroupViews: [] + m_CurrentViewIndex: 0 + m_TargetSnapshot: {fileID: 24500006} +--- !u!243 &24300002 +AudioMixerGroupController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Master + m_AudioMixer: {fileID: 24100000} + m_GroupID: b03a4a433743a1e4d97e201160f02a05 + m_Children: [] + m_Volume: 48d82f53e0dfb594bbda36fa1ca57359 + m_Pitch: f1c1eed7160108a4d87e38efebf8b26b + m_Send: 00000000000000000000000000000000 + m_Effects: + - {fileID: 24400004} + m_UserColorIndex: 0 + m_Mute: 0 + m_Solo: 0 + m_BypassEffects: 0 +--- !u!244 &24400004 +AudioMixerEffectController: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_EffectID: 52f6edff12c88324297aee83688952d8 + m_EffectName: Attenuation + m_MixLevel: 98af7356ff32408429c800bcb435171d + m_Parameters: [] + m_SendTarget: {fileID: 0} + m_EnableWetMix: 0 + m_Bypass: 0 +--- !u!245 &24500006 +AudioMixerSnapshotController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Snapshot + m_AudioMixer: {fileID: 24100000} + m_SnapshotID: fba0b3d55d166094abc6f120475969df + m_FloatValues: {} + m_TransitionOverrides: {} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer.meta new file mode 100644 index 0000000..f72d3f5 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/AudioMixer.mixer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3369d0f8fe8bcb54daee528b26ee74b9 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 24100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3 b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..af786c0ceb2b151a815e55b9d1cdd0d1d97ab1c3 GIT binary patch literal 87057 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 ZbRJMqH3H~-@9ST6B=BVtH7W?XA8Kl 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 L{Qvad?ZAHkS#HO1 literal 0 HcmV?d00001 diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3.meta new file mode 100644 index 0000000..5129e78 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/GlassBreaking.mp3.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: fd91763803673254e8e6c93e68aa4d58 +AudioImporter: + externalObjects: {} + serializedVersion: 6 + defaultSettings: + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + preloadAudioData: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3 b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a3c5c419f2b055cf12da2caa935b532d7a24e400 GIT binary patch literal 5383 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 j2@|w(#CSZI_!TJ#e^>ttCRi6g literal 0 HcmV?d00001 diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3.meta new file mode 100644 index 0000000..165a80b --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/Slicing.mp3.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: 505d16bd4b84a1c46b988504280e816a +AudioImporter: + externalObjects: {} + serializedVersion: 6 + defaultSettings: + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + preloadAudioData: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/StoneBreaking.mp3 b/Assets/Scripts/Gameplay/Fracture/Runtime/Audio/StoneBreaking.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fdc88b0687dbf021e6723fa616ad7c2a0564d9d5 GIT binary patch literal 9027 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 + /// The number of times this fragment has been re-fractured. + /// + [HideInInspector] + public int currentRefractureCount = 0; + + ///

+ /// Collector object that stores the produced fragments + /// + private GameObject fragmentRoot; + + [ContextMenu("Print Mesh Info")] + public void PrintMeshInfo() + { + var mesh = GetComponent().mesh; + Debug.Log("Positions"); + + var positions = mesh.vertices; + var normals = mesh.normals; + var uvs = mesh.uv; + + for (int i = 0; i < positions.Length; i++) + { + Debug.Log($"Vertex {i}"); + Debug.Log($"POS | X: {positions[i].x} Y: {positions[i].y} Z: {positions[i].z}"); + Debug.Log($"NRM | X: {normals[i].x} Y: {normals[i].y} Z: {normals[i].z} LEN: {normals[i].magnitude}"); + Debug.Log($"UV | U: {uvs[i].x} V: {uvs[i].y}"); + Debug.Log(""); + } + } + + void OnValidate() + { + if (transform.parent != null) + { + // When an object is fractured, the fragments are created as children of that object's parent. + // Because of this, they inherit the parent transform. If the parent transform is not scaled + // the same in all axes, the fragments will not be rendered correctly. + var scale = transform.parent.localScale; + if ((scale.x != scale.y) || (scale.x != scale.z) || (scale.y != scale.z)) + { + Debug.LogWarning($"Warning: Parent transform of fractured object must be uniformly scaled in all axes or fragments will not render correctly.", transform); + } + } + } + + void OnCollisionEnter(Collision collision) + { + if (triggerOptions.triggerType == TriggerType.Collision) + { + if (collision.contactCount > 0) + { + // Collision force must exceed the minimum force (F = I / T) + var contact = collision.contacts[0]; + // dI = F * dt + float collisionForce = collision.impulse.magnitude / Time.fixedDeltaTime; + + // Colliding object tag must be in the set of allowed collision tags if filtering by tag is enabled + bool tagAllowed = triggerOptions.IsTagAllowed(contact.otherCollider.gameObject.tag); + + // Object is unfrozen if the colliding object has the correct tag (if tag filtering is enabled) + // and the collision force exceeds the minimum collision force. + if (collisionForce > triggerOptions.minimumCollisionForce && + (!triggerOptions.filterCollisionsByTag || tagAllowed)) + { + firstHitPoint = contact; + ComputeFracture(); + } + } + } + } + + void OnTriggerEnter(Collider collider) + { + if (triggerOptions.triggerType == TriggerType.Trigger) + { + // Colliding object tag must be in the set of allowed collision tags if filtering by tag is enabled + bool tagAllowed = triggerOptions.IsTagAllowed(collider.gameObject.tag); + + if (!triggerOptions.filterCollisionsByTag || tagAllowed) + { + ComputeFracture(); + } + } + } + + void Update() + { + if (triggerOptions.triggerType == TriggerType.Keyboard) + { + if (Input.GetKeyDown(triggerOptions.triggerKey)) + { + ComputeFracture(); + } + } + } + + /// + /// Compute the fracture and create the fragments + /// + /// + private void ComputeFracture() // start fracture once condition is satisfied + { + var mesh = GetComponent().sharedMesh; + + if (mesh != null) + { + // If the fragment root object has not yet been created, create it now + if (fragmentRoot == null) + { + // Create a game object to contain the fragments + fragmentRoot = new GameObject($"{name}Fragments"); + fragmentRoot.transform.SetParent(transform.parent); + + // Each fragment will handle its own scale + fragmentRoot.transform.position = transform.position; + fragmentRoot.transform.rotation = transform.rotation; + fragmentRoot.transform.localScale = Vector3.one; + } + + var fragmentTemplate = CreateFragmentTemplate(); + + if (fractureOptions.asynchronous) + { + StartCoroutine(Fragmenter.FractureAsync( + gameObject, + fractureOptions, + fragmentTemplate, + fragmentRoot.transform, + () => + { + // Done with template, destroy it + GameObject.Destroy(fragmentTemplate); + + // Deactivate the original object + gameObject.SetActive(false); + + // Fire the completion callback + if ((currentRefractureCount == 0) || + (currentRefractureCount > 0 && refractureOptions.invokeCallbacks)) + { + if (callbackOptions.onCompleted != null) + { + callbackOptions.onCompleted.Invoke(); + } + } + } + )); + } + else + { + Fragmenter.firstHitPoint = firstHitPoint; + Fragmenter.Fracture(gameObject, + fractureOptions, + fragmentTemplate, + fragmentRoot.transform); + + // Done with template, destroy it + GameObject.Destroy(fragmentTemplate); + + // Deactivate the original object + gameObject.SetActive(false); + + // Fire the completion callback + if ((currentRefractureCount == 0) || + (currentRefractureCount > 0 && refractureOptions.invokeCallbacks)) + { + if (callbackOptions.onCompleted != null) + { + callbackOptions.onCompleted.Invoke(); + } + } + } + } + } + + /// + /// Creates a template object which each fragment will derive from + /// + /// True if this object is being pre-fractured. This will freeze all of the fragments. + /// + private GameObject CreateFragmentTemplate() + { + // If pre-fracturing, make the fragments children of this object so they can easily be unfrozen later. + // Otherwise, parent to this object's parent + GameObject obj = new GameObject(); + obj.name = "Fragment"; + obj.tag = tag; + + // Update mesh to the new sliced mesh + obj.AddComponent(); + + // Add materials. Normal material goes in slot 1, cut material in slot 2 + var meshRenderer = obj.AddComponent(); + meshRenderer.sharedMaterials = new Material[2] + { + GetComponent().sharedMaterial, + fractureOptions.insideMaterial + }; + + // Copy collider properties to fragment + var thisCollider = GetComponent(); + var fragmentCollider = obj.AddComponent(); + fragmentCollider.convex = true; + fragmentCollider.sharedMaterial = thisCollider.sharedMaterial; + fragmentCollider.isTrigger = thisCollider.isTrigger; + + // Copy rigid body properties to fragment + var thisRigidBody = GetComponent(); + var fragmentRigidBody = obj.AddComponent(); + fragmentRigidBody.velocity = thisRigidBody.velocity; + fragmentRigidBody.angularVelocity = thisRigidBody.angularVelocity; + fragmentRigidBody.drag = thisRigidBody.drag; + fragmentRigidBody.angularDrag = thisRigidBody.angularDrag; + fragmentRigidBody.useGravity = thisRigidBody.useGravity; + + // If refracturing is enabled, create a copy of this component and add it to the template fragment object + if (refractureOptions.enableRefracturing && + (currentRefractureCount < refractureOptions.maxRefractureCount)) + { + CopyFractureComponent(obj); + } + + return obj; + } + + /// + /// Convenience method for copying this component to another component + /// + /// The GameObject to copy the component to + private void CopyFractureComponent(GameObject obj) + { + var fractureComponent = obj.AddComponent(); + + fractureComponent.triggerOptions = triggerOptions; + fractureComponent.fractureOptions = fractureOptions; + fractureComponent.refractureOptions = refractureOptions; + fractureComponent.callbackOptions = callbackOptions; + fractureComponent.currentRefractureCount = currentRefractureCount + 1; + fractureComponent.fragmentRoot = fragmentRoot; + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fracture.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fracture.cs.meta new file mode 100644 index 0000000..13a6f47 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fracture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91fc9178a0b0c3d4bb8b6d91b16d9893 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment.meta new file mode 100644 index 0000000..ef194ce --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a4c8ded860f2f6b489482d866cec1a7e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs new file mode 100644 index 0000000..1e76426 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs @@ -0,0 +1,764 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Class for triangulating a set of 3D points with edge constraints. Supports convex and non-convex polygons +/// as well as polygons with holes. +/// +public sealed class ConstrainedTriangulator : Triangulator +{ + /// + /// Given an edge E12, E23, E31, this returns the first vertex for that edge (V1, V2, V3, respectively) + /// + /// + private static readonly int[] edgeVertex1 = new int[] { 0, 0, 0, V1, V2, V3 }; + + /// + /// Given an edge E12, E23, E31, this returns the second vertex for that edge (V2, V3, V1, respectively) + /// + /// + private static readonly int[] edgeVertex2 = new int[] { 0, 0, 0, V2, V3, V1 }; + + /// + /// Given an edge E12, E23, E31, this returns the vertex opposite that edge (V3, V1, V2, respectively) + /// + /// + private static readonly int[] oppositePoint = new int[] { 0, 0, 0, V3, V1, V2 }; + + /// + /// Given an edge E12, E23, E31, this returns the next clockwise edge (E23, E31, E12, respectively) + /// + /// + private static readonly int[] nextEdge = new int[] { 0, 0, 0, E23, E31, E12 }; + + /// + /// Given an edge E12, E23, E31, this returns the previous clockwise edge (E31, E12, E23, respectively) + /// + /// + private static readonly int[] previousEdge = new int[] { 0, 0, 0, E31, E12, E23 }; + + /// + /// List of edge constraints provided during initialization + /// + private List constraints; + + /// + /// This array maps each vertex to a triangle in the triangulation that contains it. This helps + /// speed up the search when looking for intersecting edge. It isn't necessary to keep track of + /// every triangle for each vertex. + /// + private int[] vertexTriangles; + + /// + /// Flag for each triangle to track whether it has been visited or not when finding the starting edge. + /// Define at the class level to prevent unnecessary GC when calling FindStartingEdge multiple times. + /// + private bool[] visited; + + /// + /// Initializes the triangulator with the vertex data to be triangulated given a set of edge constraints + /// + /// The of points to triangulate. + /// The list of edge constraints which defines how the vertices in `inputPoints` are connected. + /// The normal of the plane in which the `inputPoints` lie. + /// + public ConstrainedTriangulator(List inputPoints, List constraints, Vector3 normal) + : base(inputPoints, normal) + { + this.constraints = constraints; + } + + /// + /// Calculates the triangulation + /// + /// Returns an array containing the indices of the triangles, mapped to the list of points passed in during initialization. + public override int[] Triangulate() + { + // Need at least 3 vertices to triangulate + if (N < 3) + { + return new int[] { }; + } + + this.AddSuperTriangle(); + this.NormalizeCoordinates(); + this.ComputeTriangulation(); + + if (constraints.Count > 0) + { + this.ApplyConstraints(); + this.DiscardTrianglesViolatingConstraints(); + } + + this.DiscardTrianglesWithSuperTriangleVertices(); + + List triangles = new List(3 * triangleCount); + for (int i = 0; i < triangleCount; i++) + { + // Add all triangles that don't contain a super-triangle vertex + if (!skipTriangle[i]) + { + triangles.Add(triangulation[i, V1]); + triangles.Add(triangulation[i, V2]); + triangles.Add(triangulation[i, V3]); + } + } + + return triangles.ToArray(); + } + + /// + /// Applys the edge constraints to the triangulation + /// + internal void ApplyConstraints() + { + visited = new bool[triangulation.GetLength(0)]; + + // Map each vertex to a triangle that contains it + vertexTriangles = new int[N + 3]; + for (int i = 0; i < triangulation.GetLength(0); i++) + { + vertexTriangles[triangulation[i, V1]] = i; + vertexTriangles[triangulation[i, V2]] = i; + vertexTriangles[triangulation[i, V3]] = i; + } + + // Loop through each edge constraint + foreach (EdgeConstraint constraint in constraints) + { + if (constraint.v1 == constraint.v2) continue; + + // We find the edges of the triangulation that intersect the constraint edge and remove them + // For each intersecting edge, we identify the triangles that share that edge (which form a quad) + // The diagonal of this quad is flipped. + Queue intersectingEdges = FindIntersectingEdges(constraint, vertexTriangles); + RemoveIntersectingEdges(constraint, intersectingEdges); + } + } + + /// + /// Searches through the triangulation to find intersecting edges + /// + /// + internal Queue FindIntersectingEdges(EdgeConstraint constraint, int[] vertexTriangles) + { + Queue intersectingEdges = new Queue(); + + // Need to find the first edge that the constraint crosses. + EdgeConstraint startEdge; + if (FindStartingEdge(vertexTriangles, constraint, out startEdge)) + { + intersectingEdges.Enqueue(startEdge); + } + else + { + return intersectingEdges; + } + + // Search for all triangles that intersect the constraint. Stop when we find a triangle that contains v_j + int t = startEdge.t1; + int edgeIndex = startEdge.t1Edge; + int lastTriangle = t; + bool finalTriangleFound = false; + while (!finalTriangleFound) + { + // Cross the last intersecting edge and inspect the next triangle + lastTriangle = t; + t = triangulation[t, edgeIndex]; + + // Get coordinates of constraint end points and triangle vertices + Vector2 v_i = points[constraint.v1].coords; + Vector2 v_j = points[constraint.v2].coords; + Vector2 v1 = points[triangulation[t, V1]].coords; + Vector2 v2 = points[triangulation[t, V2]].coords; + Vector2 v3 = points[triangulation[t, V3]].coords; + + // If triangle contains the endpoint of the constraint, the search is done + if (TriangleContainsVertex(t, constraint.v2)) + { + finalTriangleFound = true; + } + // Otherwise, the constraint must intersect one edge of this triangle. Ignore the edge that we entered from + else if ((triangulation[t, E12] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v1, v2)) + { + edgeIndex = E12; + var edge = new EdgeConstraint(triangulation[t, V1], triangulation[t, V2], t, triangulation[t, E12], edgeIndex); + intersectingEdges.Enqueue(edge); + } + else if ((triangulation[t, E23] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v2, v3)) + { + edgeIndex = E23; + var edge = new EdgeConstraint(triangulation[t, V2], triangulation[t, V3], t, triangulation[t, E23], edgeIndex); + intersectingEdges.Enqueue(edge); + } + else if ((triangulation[t, E31] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v3, v1)) + { + edgeIndex = E31; + var edge = new EdgeConstraint(triangulation[t, V3], triangulation[t, V1], t, triangulation[t, E31], edgeIndex); + intersectingEdges.Enqueue(edge); + } + else + { + // Shouldn't reach this point + Debug.LogWarning("Failed to find final triangle, exiting early."); + break; + } + } + + return intersectingEdges; + } + + /// + /// Finds the starting edge for the search to find all edges that intersect the constraint + /// + /// The constraint being used to check for intersections + internal bool FindStartingEdge(int[] vertexTriangles, EdgeConstraint constraint, out EdgeConstraint startingEdge) + { + // Initialize out parameter to default value + startingEdge = new EdgeConstraint(-1, -1); + + // v_i->v_j are the start/end points of the constraint, respectively + int v_i = constraint.v1; + int v_j = constraint.v2; + + // Start the search with an initial triangle that contains v_i + int tSearch = vertexTriangles[v_i]; + + // Reset visited states + for (int i = 0; i < visited.Length; i++) + { + visited[i] = false; + } + + // Circle v_i until we find a triangle that contains an edge which intersects the constraint edge + // This will be the starting triangle in the search for finding all triangles that intersect the constraint + bool intersectionFound = false; + bool noCandidatesFound = false; + int intersectingEdgeIndex = E12; + int tE12, tE23, tE31; + while (!intersectionFound && !noCandidatesFound) + { + visited[tSearch] = true; + + // Triangulation already contains the constraint so we ignore the constraint + if (TriangleContainsConstraint(tSearch, constraint)) + { + return false; + } + // Check if the constraint intersects any edges of this triangle + else if (EdgeConstraintIntersectsTriangle(tSearch, constraint, out intersectingEdgeIndex)) + { + intersectionFound = true; + break; + } + + tE12 = triangulation[tSearch, E12]; + tE23 = triangulation[tSearch, E23]; + tE31 = triangulation[tSearch, E31]; + + // If constraint does not intersect this triangle, check adjacent triangles by crossing edges that have v_i as a vertex + // Avoid triangles that we have previously visited in the search + if (tE12 != OUT_OF_BOUNDS && !visited[tE12] && TriangleContainsVertex(tE12, v_i)) + { + tSearch = tE12; + } + else if (tE23 != OUT_OF_BOUNDS && !visited[tE23] && TriangleContainsVertex(tE23, v_i)) + { + tSearch = tE23; + } + else if (tE31 != OUT_OF_BOUNDS && !visited[tE31] && TriangleContainsVertex(tE31, v_i)) + { + tSearch = tE31; + } + else + { + noCandidatesFound = true; + break; + } + } + + if (intersectionFound) + { + int v_k = triangulation[tSearch, edgeVertex1[intersectingEdgeIndex]]; + int v_l = triangulation[tSearch, edgeVertex2[intersectingEdgeIndex]]; + int triangle2 = triangulation[tSearch, intersectingEdgeIndex]; + startingEdge = new EdgeConstraint(v_k, v_l, tSearch, triangle2, intersectingEdgeIndex); + + return true; + } + else + { + return false; + } + } + + /// + /// Remove the edges from the triangulation that intersect the constraint. Find two triangles that + /// share the intersecting edge, swap the diagonal and repeat until no edges intersect the constraint. + /// + /// The constraint to check against + /// A queue containing the previously found edges that intersect the constraint + internal void RemoveIntersectingEdges(EdgeConstraint constraint, Queue intersectingEdges) + { + // Remove intersecting edges. Keep track of the new edges that we create + List newEdges = new List(); + EdgeConstraint edge, newEdge; + + // Mark the number of times we have been through the loop. If no new edges + // have been added after all edges have been visited, stop the loop. Every + // time an edge is added to newEdges, reset the counter. + int counter = 0; + + // Loop through all intersecting edges until they have been properly resolved + // or they have all been visited with no diagonal swaps. + while (intersectingEdges.Count > 0 && counter <= intersectingEdges.Count) + { + edge = intersectingEdges.Dequeue(); + + Quad quad; + if (FindQuadFromSharedEdge(edge.t1, edge.t1Edge, out quad)) + { + // If the quad is convex, we swap the diagonal (a quad is convex if the diagonals intersect) + // Otherwise push it back into the queue so we can swap the diagonal later on. + if (MathUtils.LinesIntersect(points[quad.q4].coords, + points[quad.q3].coords, + points[quad.q1].coords, + points[quad.q2].coords)) + { + // Swap diagonals of the convex quads whose diagonals intersect the constraint + SwapQuadDiagonal(quad, intersectingEdges, newEdges, constraints); + + // The new diagonal is between Q3 and Q4 + newEdge = new EdgeConstraint(quad.q3, quad.q4, quad.t1, quad.t2, E31); + + // If the new diagonal still intersects the constraint edge v_i->v_j, + // put back on the list of intersecting eddges + if (MathUtils.LinesIntersect(points[constraint.v1].coords, + points[constraint.v2].coords, + points[quad.q3].coords, + points[quad.q4].coords)) + { + intersectingEdges.Enqueue(newEdge); + } + // Otherwise record in list of new edges + else + { + counter = 0; + newEdges.Add(newEdge); + } + } + else + { + intersectingEdges.Enqueue(edge); + } + } + + counter++; + } + + // If any new edges were formed due to a diagonal being swapped, restore the Delauney condition + // of the triangulation while respecting the constraints + if (newEdges.Count > 0) + { + RestoreConstrainedDelauneyTriangulation(constraint, newEdges); + } + } + + /// + /// Restores the Delauney triangulation after the constraint has been inserted + /// + /// The constraint that was added to the triangulation + /// The list of new edges that were added + internal void RestoreConstrainedDelauneyTriangulation(EdgeConstraint constraint, List newEdges) + { + // Iterate over the list of newly created edges and swap non-constraint diagonals until no more swaps take place + bool swapOccurred = true; + int counter = 0; + while (swapOccurred) + { + counter++; + swapOccurred = false; + + for (int i = 0; i < newEdges.Count; i++) + { + EdgeConstraint edge = newEdges[i]; + + // If newly added edge is equal to constraint, we don't want to flip this edge so skip it + if (edge == constraint) + { + continue; + } + + Quad quad; + if (FindQuadFromSharedEdge(edge.t1, edge.t1Edge, out quad)) + { + if (SwapTest(points[quad.q1].coords, points[quad.q2].coords, points[quad.q3].coords, points[quad.q4].coords)) + { + SwapQuadDiagonal(quad, newEdges, constraints, null); + + // Enqueue the new diagonal + int v_m = quad.q3; + int v_n = quad.q4; + newEdges[i] = new EdgeConstraint(v_m, v_n, quad.t1, quad.t2, E31); + + swapOccurred = true; + } + } + } + } + } + + /// + /// Discards triangles that violate the any of the edge constraints + /// + internal void DiscardTrianglesViolatingConstraints() + { + // Initialize to all triangles being skipped + for (int i = 0; i < triangleCount; i++) + { + skipTriangle[i] = true; + } + + // Identify the boundary edges + HashSet < (int, int) > boundaries = new HashSet < (int, int) > (); + for (int i = 0; i < this.constraints.Count; i++) + { + EdgeConstraint constraint = this.constraints[i]; + boundaries.Add((constraint.v1, constraint.v2)); + } + + // Reset visited states + for (int i = 0; i < visited.Length; i++) + { + visited[i] = false; + } + + // Search frontier + Queue frontier = new Queue(); + + int v1, v2, v3; + bool boundaryE12, boundaryE23, boundaryE31; + for (int i = 0; i < triangleCount; i++) + { + // If we've already visited this triangle, skip it + if (visited[i]) + { + continue; + } + + v1 = triangulation[i, V1]; + v2 = triangulation[i, V2]; + v3 = triangulation[i, V3]; + boundaryE12 = boundaries.Contains((v1, v2)); + boundaryE23 = boundaries.Contains((v2, v3)); + boundaryE31 = boundaries.Contains((v3, v1)); + + // If this triangle has a boundary edge, start searching for adjacent triangles + if (boundaryE12 || boundaryE23 || boundaryE31) + { + skipTriangle[i] = false; + + // Search along edges that are not boundary edges + frontier.Clear(); + if (!boundaryE12) + { + frontier.Enqueue(triangulation[i, E12]); + } + if (!boundaryE23) + { + frontier.Enqueue(triangulation[i, E23]); + } + if (!boundaryE31) + { + frontier.Enqueue(triangulation[i, E31]); + } + + // Recursively search along all non-boundary edges, marking the + // adjacent triangles as "keep" + while (frontier.Count > 0) + { + int k = frontier.Dequeue(); + + if (k == OUT_OF_BOUNDS || visited[k]) + { + continue; + } + + skipTriangle[k] = false; + visited[k] = true; + + v1 = triangulation[k, V1]; + v2 = triangulation[k, V2]; + v3 = triangulation[k, V3]; + + // Continue searching along non-boundary edges + if (!boundaries.Contains((v1, v2))) + { + frontier.Enqueue(triangulation[k, E12]); + } + if (!boundaries.Contains((v2, v3))) + { + frontier.Enqueue(triangulation[k, E23]); + } + if (!boundaries.Contains((v3, v1))) + { + frontier.Enqueue(triangulation[k, E31]); + } + } + } + } + } + + /// + /// Determines if the triangle contains the edge constraint + /// + /// The triangle to test + /// The edge constraint + /// True if the triangle contains one or both of the endpoints of the constraint + internal bool TriangleContainsConstraint(int t, EdgeConstraint constraint) + { + return (triangulation[t, V1] == constraint.v1 || triangulation[t, V2] == constraint.v1 || triangulation[t, V3] == constraint.v1) && + (triangulation[t, V1] == constraint.v2 || triangulation[t, V2] == constraint.v2 || triangulation[t, V3] == constraint.v2); + } + + /// + /// Returns true if the edge constraint intersects an edge of triangle `t` + /// + /// The triangle to test + /// The edge constraint + /// The index of the intersecting edge (E12, E23, E31) + /// Returns true if an intersection is found, otherwise false. + internal bool EdgeConstraintIntersectsTriangle(int t, EdgeConstraint constraint, out int intersectingEdgeIndex) + { + Vector2 v_i = points[constraint.v1].coords; + Vector2 v_j = points[constraint.v2].coords; + Vector2 v1 = points[triangulation[t, V1]].coords; + Vector2 v2 = points[triangulation[t, V2]].coords; + Vector2 v3 = points[triangulation[t, V3]].coords; + + if (MathUtils.LinesIntersect(v_i, v_j, v1, v2)) + { + intersectingEdgeIndex = E12; + return true; + } + else if (MathUtils.LinesIntersect(v_i, v_j, v2, v3)) + { + intersectingEdgeIndex = E23; + return true; + } + else if (MathUtils.LinesIntersect(v_i, v_j, v3, v1)) + { + intersectingEdgeIndex = E31; + return true; + } + else + { + intersectingEdgeIndex = -1; + return false; + } + } + + /// + /// Returns the quad formed by triangle `t1` and the other triangle that shares the intersecting edge + /// + /// Base triangle + /// Edge index that is being intersected + internal bool FindQuadFromSharedEdge(int t1, int t1SharedEdge, out Quad quad) + { + // q3 + // *---------*---------* + // \ / \ / + // \ t2L / \ t2R / + // \ / \ / + // \ / t2 \ / + // q1 *---------* q2 + // / \ t1 / \ + // / \ / \ + // / t1L \ / t1R \ + // / \ / \ + // *---------*---------* + // q4 + + int q1, q2, q3, q4; + int t1L, t1R, t2L, t2R; + + // t2 is adjacent to t1 along t1Edge + int t2 = triangulation[t1, t1SharedEdge]; + int t2SharedEdge; + if (FindSharedEdge(t2, t1, out t2SharedEdge)) + { + // Get the top 3 vertices of the quad from t2 + if (t2SharedEdge == E12) + { + q2 = triangulation[t2, V1]; + q1 = triangulation[t2, V2]; + q3 = triangulation[t2, V3]; + } + else if (t2SharedEdge == E23) + { + q2 = triangulation[t2, V2]; + q1 = triangulation[t2, V3]; + q3 = triangulation[t2, V1]; + } + else // (t2SharedEdge == E31) + { + q2 = triangulation[t2, V3]; + q1 = triangulation[t2, V1]; + q3 = triangulation[t2, V2]; + } + + // q4 is the point in t1 opposite of the shared edge + q4 = triangulation[t1, oppositePoint[t1SharedEdge]]; + + // Get the adjacent triangles to make updating adjacency easier + t1L = triangulation[t1, previousEdge[t1SharedEdge]]; + t1R = triangulation[t1, nextEdge[t1SharedEdge]]; + t2L = triangulation[t2, nextEdge[t2SharedEdge]]; + t2R = triangulation[t2, previousEdge[t2SharedEdge]]; + + quad = new Quad(q1, q2, q3, q4, t1, t2, t1L, t1R, t2L, t2R); + + return true; + } + + quad = new Quad(); + + return false; + } + + /// + /// Swaps the diagonal of the quadrilateral q0->q1->q2->q3 formed by t1 and t2 + /// + /// The quad that will have its diagonal swapped + internal void SwapQuadDiagonal(Quad quad, IEnumerable edges1, IEnumerable edges2, IEnumerable edges3) + { + // BEFORE + // q3 + // *---------*---------* + // \ / \ / + // \ t2L / \ t2R / + // \ / \ / + // \ / t2 \ / + // q1 *---------* q2 + // / \ t1 / \ + // / \ / \ + // / t1L \ / t1R \ + // / \ / \ + // *---------*---------* + // q4 + + // AFTER + // q3 + // *---------*---------* + // \ /|\ / + // \ t2L / | \ t2R / + // \ / | \ / + // \ / | \ / + // q1 * t1 | t2 * q2 + // / \ | / \ + // / \ | / \ + // / t1L \ | / t1R \ + // / \|/ \ + // *---------*---------* + // q4 + + int t1 = quad.t1; + int t2 = quad.t2; + int t1R = quad.t1R; + int t1L = quad.t1L; + int t2R = quad.t2R; + int t2L = quad.t2L; + + // Perform the swap. As always, put the new vertex as the first vertex of the triangle + triangulation[t1, V1] = quad.q4; + triangulation[t1, V2] = quad.q1; + triangulation[t1, V3] = quad.q3; + + triangulation[t2, V1] = quad.q4; + triangulation[t2, V2] = quad.q3; + triangulation[t2, V3] = quad.q2; + + triangulation[t1, E12] = t1L; + triangulation[t1, E23] = t2L; + triangulation[t1, E31] = t2; + + triangulation[t2, E12] = t1; + triangulation[t2, E23] = t2R; + triangulation[t2, E31] = t1R; + + // Update adjacency for the adjacent triangles + UpdateAdjacency(t2L, t2, t1); + UpdateAdjacency(t1R, t1, t2); + + // Now that triangles have moved, need to update edges as well + UpdateEdgesAfterSwap(edges1, t1, t2, t1L, t1R, t2L, t2R); + UpdateEdgesAfterSwap(edges2, t1, t2, t1L, t1R, t2L, t2R); + UpdateEdgesAfterSwap(edges3, t1, t2, t1L, t1R, t2L, t2R); + + // Also need to update the vertexTriangles array since the vertices q1 and q2 + // may have been referencing t2/t1 respectively and they are no longer. + vertexTriangles[quad.q1] = t1; + vertexTriangles[quad.q2] = t2; + } + + /// + /// Update the Edges + /// + /// + /// + /// + /// + /// + /// + /// + internal void UpdateEdgesAfterSwap(IEnumerable edges, int t1, int t2, int t1L, int t1R, int t2L, int t2R) + { + if (edges == null) + { + return; + } + + // Update edges to reflect changes in triangles + foreach (EdgeConstraint edge in edges) + { + if (edge.t1 == t1 && edge.t2 == t1R) + { + edge.t1 = t2; + edge.t2 = t1R; + edge.t1Edge = E31; + } + else if (edge.t1 == t1 && edge.t2 == t1L) + { + // Triangles stay the same + edge.t1Edge = E12; + } + else if (edge.t1 == t1R && edge.t2 == t1) + { + edge.t2 = t2; + } + else if (edge.t1 == t1L && edge.t2 == t1) + { + // Unchanged + } + else if (edge.t1 == t2 && edge.t2 == t2R) + { + // Triangles stay the same + edge.t1Edge = E23; + } + else if (edge.t1 == t2 && edge.t2 == t2L) + { + edge.t1 = t1; + edge.t2 = t2L; + edge.t1Edge = E23; + } + else if (edge.t1 == t2R && edge.t2 == t2) + { + // Unchanged + } + else if (edge.t1 == t2L && edge.t2 == t2) + { + edge.t2 = t1; + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs.meta new file mode 100644 index 0000000..6447907 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/ConstrainedTriangulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dae513e54953d9740a998fa1d26020b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs new file mode 100644 index 0000000..d8dd993 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs @@ -0,0 +1,87 @@ +using UnityEngine.TestTools; + +/// +/// Represents an edge constraint between two vertices in the triangulation +/// +public class EdgeConstraint +{ + /// + /// Index of the first end point of the constraint + /// + public int v1; + + /// + /// Index of the second end point of the constraint + /// + public int v2; + + /// + /// Index of the triangle prior to the edge crossing (v1 -> v2) + /// + public int t1; + + /// + /// Index of the triangle after the edge crossing (v1 -> v2) + /// + public int t2; + + /// + /// Index of the edge on the t1 side + /// + public int t1Edge; + + /// + /// Creates a new edge constraint with the given end points + /// + public EdgeConstraint(int v1, int v2) + { + this.v1 = v1; + this.v2 = v2; + this.t1 = -1; + this.t2 = -1; + } + + /// + /// Creates a new edge constraint and defines triangles on either side of the edge + /// + public EdgeConstraint(int v1, int v2, int triangle1, int triangle2, int edge1) + { + this.v1 = v1; + this.v2 = v2; + this.t1 = triangle1; + this.t2 = triangle2; + this.t1Edge = edge1; + } + + public override bool Equals(object obj) + { + if (obj is EdgeConstraint) + { + var other = (EdgeConstraint)obj; + return (this.v1 == other.v1 && this.v2 == other.v2) || + (this.v1 == other.v2 && this.v2 == other.v1); + } + return false; + } + + public override int GetHashCode() + { + return new { v1, v2 }.GetHashCode() + new { v2, v1 }.GetHashCode(); + } + + public static bool operator ==(EdgeConstraint lhs, EdgeConstraint rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(EdgeConstraint lhs, EdgeConstraint rhs) + { + return !lhs.Equals(rhs); + } + + [ExcludeFromCoverage] + public override string ToString() + { + return $"Edge: T{t1}->T{t2} (V{v1}->V{v2})"; + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs.meta new file mode 100644 index 0000000..fe2bc2a --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/EdgeConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e465c2db3ee42004bb2588140d2c0275 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs new file mode 100644 index 0000000..c50c80b --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs @@ -0,0 +1,317 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +public enum SlicedMeshSubmesh +{ + Default = 0, + CutFace = 1 +} + +/// +/// Data structure used for storing mesh data during the fragmenting process +/// +public class FragmentData +{ + /// + /// Vertex buffer for the non-cut mesh faces + /// + public List Vertices; + + /// + /// Vertex buffer for the cut mesh faces + /// + public List CutVertices; + + /// + /// Index buffer for each submesh + /// + public List[] Triangles; // = Primitives + + /// + /// List of edges constraints for the cut-face triangulation + /// + public List Constraints; + + /// + /// Map between vertex indices in the source mesh and new indices for the sliced mesh + /// + public int[] IndexMap; + + /// + /// The bounds of the vertex data (must manually call UpdateBounds() to update) + /// + public Bounds Bounds; + + /// + /// Gets the total number of triangles across all sub meshes + /// + /// + public int triangleCount + { + get + { + int count = 0; + foreach (List tri in this.Triangles) + { + count += tri.Count; + } + return count; + } + } + + /// + /// Gets the total number of vertices in the mesh + /// + /// + public int vertexCount + { + get + { + return this.Vertices.Count + this.CutVertices.Count; + } + } + + /// + /// Initializes a new sliced mesh + /// + /// The name of the mesh + /// Vertex count used to initialize lists. Initializing lists to approximate size reduces resizes and GC. + /// Triangle count used to initialize lists. Initializing lists to approximate size reduces resizes and GC. + public FragmentData(int vertexCount, int triangleCount) + { + this.Vertices = new List(vertexCount); + this.CutVertices = new List(vertexCount / 10); + + // Store triangles for each submesh separately + this.Triangles = new List[] + { + new List(triangleCount), + new List(triangleCount / 10) + }; + + this.Constraints = new List(); + this.IndexMap = new int[vertexCount]; + } + + /// + /// Creates a new sliced mesh dataset from source mesh data + /// + /// The source mesh data. + public FragmentData(Mesh mesh) + { + var positions = mesh.vertices; + var normals = mesh.normals; + var uv = mesh.uv; + + this.Vertices = new List(mesh.vertexCount); + this.CutVertices = new List(mesh.vertexCount / 10); + this.Constraints = new List(); + this.IndexMap = new int[positions.Length]; + + // Add mesh vertices + for (int i = 0; i < positions.Length; i++) + { + this.Vertices.Add(new MeshVertex(positions[i], normals[i], uv[i])); + } + + // Only meshes with one submesh are currently supported + this.Triangles = new List[2]; + this.Triangles[0] = new List(mesh.GetTriangles(0)); + + if (mesh.subMeshCount >= 2) + { + this.Triangles[1] = new List(mesh.GetTriangles(1)); + } + else + { + this.Triangles[1] = new List(mesh.triangles.Length / 10); + } + + this.CalculateBounds(); + } + + /// + /// Adds a new cut face vertex + /// + /// The vertex position + /// The vertex normal + /// The vertex UV coordinates + /// Returns the index of the vertex in the cutVertices array + public void AddCutFaceVertex(Vector3 position, Vector3 normal, Vector2 uv) + { + var vertex = new MeshVertex(position, normal, uv); + + // Add the vertex to both the normal mesh vertex data and the cut face vertex data + // The vertex on the cut face will have different normal/uv coordinates which are + // populated with the correct values later in the triangulation process. + this.Vertices.Add(vertex); + this.CutVertices.Add(vertex); + } + + /// + /// Adds a new vertex to this mesh that is mapped to the source mesh + /// + /// Vertex data + /// Index of the vertex in the source mesh + public void AddMappedVertex(MeshVertex vertex, int sourceIndex) + { + this.Vertices.Add(vertex); + this.IndexMap[sourceIndex] = this.Vertices.Count - 1; + } + + /// + /// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indexes of the + /// vertices relative to this mesh's list of vertices; no mapping is performed. + /// + /// Index of the first vertex + /// Index of the second vertex + /// Index of the third vertex + /// The sub-mesh to add the triangle to + public void AddTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh) + { + this.Triangles[(int)subMesh].Add(v1); + this.Triangles[(int)subMesh].Add(v2); + this.Triangles[(int)subMesh].Add(v3); + } + + /// + /// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indices of the + /// vertices in the original mesh. These vertices are mapped to the indices in the sliced mesh. + /// + /// Index of the first vertex + /// Index of the second vertex + /// Index of the third vertex + /// The sub-mesh to add the triangle to + public void AddMappedTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh) + { + this.Triangles[(int)subMesh].Add(IndexMap[v1]); + this.Triangles[(int)subMesh].Add(IndexMap[v2]); + this.Triangles[(int)subMesh].Add(IndexMap[v3]); + } + + /// + /// Finds coincident vertices on the cut face and welds them together. + /// + public void WeldCutFaceVertices() + { + // Temporary array containing the unique (welded) vertices + // Initialize capacity to current number of cut vertices to prevent + // unnecessary reallocations + List weldedVerts = new List(CutVertices.Count); + + // We also keep track of the index mapping between the skipped vertices + // and the index of the welded vertex so we can update the edges + int[] indexMap = new int[CutVertices.Count]; + + // Number of welded vertices in the temp array + int k = 0; + + // Loop through each vertex, identifying duplicates. Must compare directly + // because floating point inconsistencies cause a hash table to be unreliable + // for vertices that are very close together but not directly coincident + for (int i = 0; i < CutVertices.Count; i++) + { + bool duplicate = false; + for (int j = 0; j < weldedVerts.Count; j++) + { + if (CutVertices[i].position == weldedVerts[j].position) + { + indexMap[i] = j; + duplicate = true; + break; + } + } + + if (!duplicate) + { + weldedVerts.Add(CutVertices[i]); + indexMap[i] = k; + k++; + } + } + + // Update the edges + for (int i = 0; i < Constraints.Count; i++) + { + var edge = Constraints[i]; + edge.v1 = indexMap[edge.v1]; + edge.v2 = indexMap[edge.v2]; + } + + weldedVerts.TrimExcess(); + + // Update the cut vertices + this.CutVertices = new List(weldedVerts); + } + + /// + /// Gets the triangles for the specified sub mesh + /// + /// The index of the submesh + /// + public int[] GetTriangles(int subMeshIndex) + { + return this.Triangles[subMeshIndex].ToArray(); + } + + /// + /// Calculates the bounds of the mesh data + /// + public void CalculateBounds() // + { + float vertexCount = (float)Vertices.Count; + Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); + + // The cut face does not modify the extents of the object, so we only need to + // loop through the original vertices to determine the bounds + foreach (MeshVertex vertex in Vertices) + { + if (vertex.position.x < min.x) min.x = vertex.position.x; + if (vertex.position.y < min.y) min.y = vertex.position.y; + if (vertex.position.z < min.z) min.z = vertex.position.z; + + if (vertex.position.x > max.x) max.x = vertex.position.x; + if (vertex.position.y > max.y) max.y = vertex.position.y; + if (vertex.position.z > max.z) max.z = vertex.position.z; + } + + this.Bounds = new Bounds((max + min) / 2f, max - min); + } + + /// + /// Converts the sliced mesh data into a mesh + /// + /// Returns the mesh object + public Mesh ToMesh() + { + Mesh mesh = new Mesh(); + + var layout = new[] + { + new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), + new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), + new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2), + }; + + mesh.SetIndexBufferParams(triangleCount, IndexFormat.UInt32); + mesh.SetVertexBufferParams(vertexCount, layout); + mesh.SetVertexBufferData(Vertices, 0, 0, Vertices.Count); + mesh.SetVertexBufferData(CutVertices, 0, Vertices.Count, CutVertices.Count); + + mesh.subMeshCount = Triangles.Length; + int indexStart = 0; + for (int i = 0; i < Triangles.Length; i++) + { + var subMeshIndexBuffer = Triangles[i]; + mesh.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, subMeshIndexBuffer.Count); + mesh.SetSubMesh(i, new SubMeshDescriptor(indexStart, subMeshIndexBuffer.Count)); + indexStart += subMeshIndexBuffer.Count; + } + + mesh.RecalculateBounds(); + + return mesh; + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs.meta new file mode 100644 index 0000000..c5d3373 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/FragmentData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 744e2e7cedf47b848b91e43dbf53e383 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs new file mode 100644 index 0000000..76a7d0b --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using Random = UnityEngine.Random; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +public static class Fragmenter +{ + public static ContactPoint firstHitPoint; + + /// + /// Generates the mesh fragments based on the provided options. The generated fragment objects are + /// stored as children of `fragmentParent` + /// + /// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider. + /// Options for the fragmenter + /// The template GameObject that each fragment will clone + /// The parent transform for the fragment objects + /// If true, the generated fragment meshes will be saved to disk so they can be re-used in prefabs. + /// The save location for the fragments. + /// + public static void Fracture(GameObject sourceObject, + FractureOptions options, + GameObject fragmentTemplate, + Transform parent, + bool saveToDisk = false, + string saveFolderPath = "" + ) + { + // Define our source mesh data for the fracturing + FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh); + + // We begin by fragmenting the source mesh, then process each fragment in a FIFO queue + // until we achieve the target fragment count. + var fragments = new Queue(); + fragments.Enqueue(sourceMesh); + + // Subdivide the mesh into multiple fragments until we reach the fragment limit + FragmentData topSlice, bottomSlice; + while (fragments.Count < options.fragmentCount) + { + FragmentData meshData = fragments.Dequeue(); + meshData.CalculateBounds(); + + // Select an arbitrary fracture plane normal + // todo: choose the normal based on the collision hit point + + Vector3 normal = new Vector3( + options.xAxis ? Random.Range(-1f, 1f) : 0f, + options.yAxis ? Random.Range(-1f, 1f) : 0f, + options.zAxis ? Random.Range(-1f, 1f) : 0f); + + var hitPoint = meshData.Bounds.center; + // the first slice at the point of first hit + if (fragments.Count == 0) + hitPoint = firstHitPoint.point; + + // Slice and dice! + MeshSlicer.Slice(meshData, + normal, + // meshData.Bounds.center, + hitPoint, + options.textureScale, + options.textureOffset, + out topSlice, + out bottomSlice); + + + + fragments.Enqueue(topSlice); + fragments.Enqueue(bottomSlice); + + } + + int i = 0; + foreach (FragmentData meshData in fragments) + { + CreateFragment(meshData, + sourceObject, + fragmentTemplate, + parent, + saveToDisk, + saveFolderPath, + options.detectFloatingFragments, + ref i); + } + } + + /// + /// Asynchronously generates the mesh fragments based on the provided options. The generated fragment objects are + /// stored as children of `fragmentParent` + /// + /// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider. + /// Options for the fragmenter + /// The template GameObject that each fragment will clone + /// The parent transform for the fragment objects + /// + public static IEnumerator FractureAsync(GameObject sourceObject, + FractureOptions options, + GameObject fragmentTemplate, + Transform parent, + Action onCompletion) + { + // Define our source mesh data for the fracturing + FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh); + + // We begin by fragmenting the source mesh, then process each fragment in a FIFO queue + // until we achieve the target fragment count. + var fragments = new Queue(); + fragments.Enqueue(sourceMesh); + + // Subdivide the mesh into multiple fragments until we reach the fragment limit + FragmentData topSlice, bottomSlice; + while (fragments.Count < options.fragmentCount) + { + FragmentData meshData = fragments.Dequeue(); + meshData.CalculateBounds(); + + // Select an arbitrary fracture plane normal + Vector3 normal = new Vector3( + options.xAxis ? Random.Range(-1f, 1f) : 0f, + options.yAxis ? Random.Range(-1f, 1f) : 0f, + options.zAxis ? Random.Range(-1f, 1f) : 0f); + + // Slice and dice! + MeshSlicer.Slice(meshData, + normal, + meshData.Bounds.center, + options.textureScale, + options.textureOffset, + out topSlice, + out bottomSlice); + + // Perform next slice on the next frame + yield return null; + + fragments.Enqueue(topSlice); + fragments.Enqueue(bottomSlice); + } + + int i = 0; + foreach (FragmentData meshData in fragments) + { + CreateFragment(meshData, + sourceObject, + fragmentTemplate, + parent, + false, + "", + options.detectFloatingFragments, + ref i); + } + + onCompletion?.Invoke(); + } + + /// + /// Generates the mesh fragments based on the provided options. The generated fragment objects are + /// stored as children of `fragmentParent` + /// + /// The source object to slice. This object must have a MeshFilter, a RigidBody and a Collider. + /// The normal of the cut plane in the local frame of sourceObject. + /// The origin of the cut plane in the local frame of sourceObject. + /// Options for the slicer + /// The template GameObject that each slice will clone + /// The parent transform for the fragment objects + /// + public static void Slice(GameObject sourceObject, + Vector3 sliceNormal, + Vector3 sliceOrigin, + SliceOptions options, + GameObject fragmentTemplate, + Transform parent) + { + // Define our source mesh data for the fracturing + FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent().sharedMesh); + // Subdivide the mesh into multiple fragments until we reach the fragment limit + FragmentData topSlice, bottomSlice; + + // Slice and dice! + MeshSlicer.Slice(sourceMesh, + sliceNormal, + sliceOrigin, + options.textureScale, + options.textureOffset, + out topSlice, + out bottomSlice); + + int i = 0; + CreateFragment(topSlice, + sourceObject, + fragmentTemplate, + parent, + false, + "", + options.detectFloatingFragments, + ref i); + + CreateFragment(bottomSlice, + sourceObject, + fragmentTemplate, + parent, + false, + "", + options.detectFloatingFragments, + ref i); + } + + /// + /// Creates a new GameObject from the fragment data + /// + /// Geometry of the fragment produced by the slicer + /// The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider. + /// The template GameObject that each fragment will clone + /// The parent transform for the fragment objects + /// Fragment counter + private static void CreateFragment(FragmentData fragmentMeshData, + GameObject sourceObject, + GameObject fragmentTemplate, + Transform parent, + bool saveToDisk, + string saveFolderPath, + bool detectFloatingFragments, + ref int i) + { + // If there is no mesh data, don't create an object + if (fragmentMeshData.Triangles.Length == 0) return; + + + Mesh[] meshes; + Mesh fragmentMesh = fragmentMeshData.ToMesh(); + + // If the "Detect Floating Fragments" option is enabled, take the fragment mesh and + // identify disconnected sets of geometry within it, treating each of these as a + // separate physical object + if (detectFloatingFragments) + { + // +------+ + // | | + // | +------+ + // | | | floating mesh + // | +------+ + // | | + // +------+ + meshes = MeshUtils.FindDisconnectedMeshes(fragmentMesh); + } + else + { + meshes = new Mesh[] { fragmentMesh }; + } + + var parentSize = sourceObject.GetComponent().sharedMesh.bounds.size; + var parentMass = sourceObject.GetComponent().mass; + + for (int k = 0; k < meshes.Length; k++) + { + GameObject fragment = GameObject.Instantiate(fragmentTemplate, parent); + fragment.name = $"Fragment{i}"; + fragment.transform.localPosition = Vector3.zero; + fragment.transform.localRotation = Quaternion.identity; + fragment.transform.localScale = sourceObject.transform.localScale; + + meshes[k].name = System.Guid.NewGuid().ToString(); + + // Update mesh to the new sliced mesh + var meshFilter = fragment.GetComponent(); + meshFilter.sharedMesh = meshes[k]; + + var collider = fragment.GetComponent(); + + // If fragment collisions are disabled, collider will be null + collider.sharedMesh = meshes[k]; + collider.convex = true; + collider.sharedMaterial = fragment.GetComponent().sharedMaterial; + + // Compute mass of the sliced object by dividing mesh bounds by density + var parentRigidBody = sourceObject.GetComponent(); + var rigidBody = fragment.GetComponent(); + + var size = fragmentMesh.bounds.size; + float density = (parentSize.x * parentSize.y * parentSize.z) / parentMass; + rigidBody.mass = (size.x * size.y * size.z) / density; + + // This code only compiles for the editor +#if UNITY_EDITOR + if (saveToDisk) + { + string path = $"{saveFolderPath}/{meshes[k].name}.asset"; + AssetDatabase.CreateAsset(meshes[k], path); + } +#endif + + i++; + } + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs.meta new file mode 100644 index 0000000..2a223bf --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Fragmenter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7d041b547ef59d47846c5a02021d424 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs new file mode 100644 index 0000000..6d6914c --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs @@ -0,0 +1,320 @@ +using UnityEngine; + +/// +/// Class which handles slicing a mesh into two pieces given the origin and normal of the slice plane. +/// +public static class MeshSlicer +{ + /// + /// Slices the mesh by the plane specified by `sliceNormal` and `sliceOrigin` + /// The sliced mesh data is return via out parameters. + /// + /// + /// The normal of the slice plane (points towards the top slice) + /// The origin of the slice plane + /// Scale factor to apply to UV coordinates + /// Offset to apply to UV coordinates + /// Out parameter returning fragment mesh data for slice above the plane + /// Out parameter returning fragment mesh data for slice below the plane + public static void Slice(FragmentData meshData, + Vector3 sliceNormal, + Vector3 sliceOrigin, + Vector2 textureScale, + Vector2 textureOffset, + out FragmentData topSlice, + out FragmentData bottomSlice) + { + topSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount); + bottomSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount); + + // Keep track of what side of the cutting plane each vertex is on + // side True: above the plane + // side False: below the plane + bool[] side = new bool[meshData.vertexCount]; + + // Go through and identify which vertices are above/below the split plane + for (int i = 0; i < meshData.Vertices.Count; i++) + { + var vertex = meshData.Vertices[i]; + side[i] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin); + var slice = side[i] ? topSlice : bottomSlice; + slice.AddMappedVertex(vertex, i); + } + + int offset = meshData.Vertices.Count; + for (int i = 0; i < meshData.CutVertices.Count; i++) + { + var vertex = meshData.CutVertices[i]; + side[i + offset] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin); + var slice = side[i + offset] ? topSlice : bottomSlice; + slice.AddMappedVertex(vertex, i + offset); + } + + SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.Default); + SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.CutFace); + + // Fill in the cut plane for each mesh. + // The slice normal points to the "above" mesh, so the face normal for the cut face + // on the above mesh is opposite of the slice normal. Conversely, normal for the + // cut face on the "below" mesh is in the direction of the slice normal + FillCutFaces(topSlice, bottomSlice, -sliceNormal, textureScale, textureOffset); + } + + /// + /// Fills the cut faces for each sliced mesh. The `sliceNormal` is the normal for the plane and points + /// in the direction of `topMeshData` + /// + /// Fragment mesh data for slice above the slice plane + /// Fragment mesh data for slice above the slice plane + /// Normal of the slice plane (points towards the top slice) + /// Scale factor to apply to UV coordinates + /// Offset to apply to UV coordinates + private static void FillCutFaces(FragmentData topSlice, + FragmentData bottomSlice, + Vector3 sliceNormal, + Vector2 textureScale, + Vector2 textureOffset) + { + // Since the topSlice and bottomSlice both share the same cut face, we only need to calculate it + // once. Then the same vertex/triangle data for the face will be used for both slices, except + // with the normals reversed. + + // First need to weld the coincident vertices for the triangulation to work properly + topSlice.WeldCutFaceVertices(); + + // Need at least 3 vertices to triangulate + if (topSlice.CutVertices.Count < 3) return; + + // Triangulate the cut face + var triangulator = new ConstrainedTriangulator(topSlice.CutVertices, topSlice.Constraints, sliceNormal); + int[] triangles = triangulator.Triangulate(); + + // Update normal and UV for the cut face vertices + for (int i = 0; i < topSlice.CutVertices.Count; i++) + { + var vertex = topSlice.CutVertices[i]; + var point = triangulator.points[i]; + + // UV coordinates are based off of the 2D coordinates used for triangulation + // During triangulation, coordinates are normalized to [0,1], so need to multiply + // by normalization scale factor to get back to the appropritate scale + Vector2 uv = new Vector2( + (triangulator.normalizationScaleFactor * point.coords.x) * textureScale.x + textureOffset.x, + (triangulator.normalizationScaleFactor * point.coords.y) * textureScale.y + textureOffset.y); + + // Update normals and UV coordinates for the cut vertices + var topVertex = vertex; + topVertex.normal = sliceNormal; + topVertex.uv = uv; + + var bottomVertex = vertex; + bottomVertex.normal = -sliceNormal; + bottomVertex.uv = uv; + + topSlice.CutVertices[i] = topVertex; + bottomSlice.CutVertices[i] = bottomVertex; + } + + // Add the new triangles to the top/bottom slices + int offsetTop = topSlice.Vertices.Count; + int offsetBottom = bottomSlice.Vertices.Count; + for (int i = 0; i < triangles.Length; i += 3) + { + topSlice.AddTriangle( + offsetTop + triangles[i], + offsetTop + triangles[i + 1], + offsetTop + triangles[i + 2], + SlicedMeshSubmesh.CutFace); + + bottomSlice.AddTriangle( + offsetBottom + triangles[i], + offsetBottom + triangles[i + 2], // Swap two vertices so triangles are wound CW + offsetBottom + triangles[i + 1], + SlicedMeshSubmesh.CutFace); + } + } + + /// + /// Identifies triangles that are intersected by the slice plane and splits them in two + /// + /// + /// Fragment mesh data for slice above the slice plane + /// Fragment mesh data for slice above the slice plane + /// The normal of the slice plane (points towards the top slice) + /// The origin of the slice plane + /// Array mapping each vertex to either the top/bottom slice + /// Index of the sub mesh + private static void SplitTriangles(FragmentData meshData, + FragmentData topSlice, + FragmentData bottomSlice, + Vector3 sliceNormal, + Vector3 sliceOrigin, + bool[] side, + SlicedMeshSubmesh subMesh) + { + int[] triangles = meshData.GetTriangles((int)subMesh); + + // Keep track of vertices that lie on the intersection plane + int a, b, c; + for (int i = 0; i < triangles.Length; i += 3) + { + // Get vertex indexes for this triangle + a = triangles[i]; + b = triangles[i + 1]; + c = triangles[i + 2]; + + // Triangle is contained completely within mesh A + if (side[a] && side[b] && side[c]) + { + topSlice.AddMappedTriangle(a, b, c, subMesh); + } + // Triangle is contained completely within mesh B + else if (!side[a] && !side[b] && !side[c]) + { + bottomSlice.AddMappedTriangle(a, b, c, subMesh); + } + // Triangle is intersected by the slicing plane. Need to subdivide it + else + { + // In these cases, two vertices of the triangle are above the cut plane and one vertex is below + if (side[b] && side[c] && !side[a]) + { + SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); + } + else if (side[c] && side[a] && !side[b]) + { + SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); + } + else if (side[a] && side[b] && !side[c]) + { + SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true); + } + // In these cases, two vertices of the triangle are below the cut plane and one vertex is above + else if (!side[b] && !side[c] && side[a]) + { + SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); + } + else if (!side[c] && !side[a] && side[b]) + { + SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); + } + else if (!side[a] && !side[b] && side[c]) + { + SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false); + } + } + } + } + + /// + /// Splits triangle defined by the points (v1,v2,v3) + /// + /// Index of first vertex in triangle + /// Index of second vertex in triangle< + /// Index of third vertex in triangle< + /// The normal of the slice plane (points towards the top slice) + /// The origin of the slice plane + /// Original mesh data + /// Mesh data for top slice + /// Mesh data for bottom slice + /// Index of the submesh that the triangle belongs to + /// Boolean indicating whether v3 is above or below the slice plane. + private static void SplitTriangle(int v1_idx, + int v2_idx, + int v3_idx, + Vector3 sliceNormal, + Vector3 sliceOrigin, + FragmentData meshData, + FragmentData topSlice, + FragmentData bottomSlice, + SlicedMeshSubmesh subMesh, + bool v3BelowCutPlane) + { + // - `v1`, `v2`, `v3` are the indexes of the triangle relative to the original mesh data + // - `v1` and `v2` are on the the side of split plane that belongs to meshA + // - `v3` is on the side of the split plane that belongs to meshB + // - `vertices`, `normals`, `uv` are the original mesh data used for interpolation + // + // v3BelowCutPlane = true + // ====================== + // + // v1 *_____________* v2 . + // \ / /|\ cutNormal + // \ / | + // ----*-------*---------*-- + // v13 \ / v23 cutOrigin + // \ / + // \ / + // * v3 triangle normal out of screen + // + // v3BelowCutPlane = false + // ======================= + // + // * v3 . + // / \ /|\ cutNormal + // v23 / \ v13 | + // -----*-----*----------*-- + // / \ cut origin + // / \ + // v2 *___________* v1 triangle normal out of screen + // + + float s13; + float s23; + Vector3 v13; + Vector3 v23; + + MeshVertex v1 = v1_idx < meshData.Vertices.Count ? meshData.Vertices[v1_idx] : meshData.CutVertices[v1_idx - meshData.Vertices.Count]; + MeshVertex v2 = v2_idx < meshData.Vertices.Count ? meshData.Vertices[v2_idx] : meshData.CutVertices[v2_idx - meshData.Vertices.Count]; + MeshVertex v3 = v3_idx < meshData.Vertices.Count ? meshData.Vertices[v3_idx] : meshData.CutVertices[v3_idx - meshData.Vertices.Count]; + + if (MathUtils.LinePlaneIntersection(v1.position, v3.position, sliceNormal, sliceOrigin, out v13, out s13) && + MathUtils.LinePlaneIntersection(v2.position, v3.position, sliceNormal, sliceOrigin, out v23, out s23)) + { + // Interpolate normals and UV coordinates + var norm13 = (v1.normal + s13 * (v3.normal - v1.normal)).normalized; + var norm23 = (v2.normal + s23 * (v3.normal - v2.normal)).normalized; + var uv13 = v1.uv + s13 * (v3.uv - v1.uv); + var uv23 = v2.uv + s23 * (v3.uv - v2.uv); + + // Add vertices/normals/uv for the intersection points to each mesh + topSlice.AddCutFaceVertex(v13, norm13, uv13); + topSlice.AddCutFaceVertex(v23, norm23, uv23); + bottomSlice.AddCutFaceVertex(v13, norm13, uv13); + bottomSlice.AddCutFaceVertex(v23, norm23, uv23); + + // Indices for the intersection vertices (for the original mesh data) + int index13_A = topSlice.Vertices.Count - 2; + int index23_A = topSlice.Vertices.Count - 1; + int index13_B = bottomSlice.Vertices.Count - 2; + int index23_B = bottomSlice.Vertices.Count - 1; + + if (v3BelowCutPlane) + { + // Triangle slice above the cutting plane is a quad, so divide into two triangles + topSlice.AddTriangle(index23_A, index13_A, topSlice.IndexMap[v2_idx], subMesh); + topSlice.AddTriangle(index13_A, topSlice.IndexMap[v1_idx], topSlice.IndexMap[v2_idx], subMesh); + + // One triangle must be added to mesh 2 + bottomSlice.AddTriangle(bottomSlice.IndexMap[v3_idx], index13_B, index23_B, subMesh); + + // When looking at the cut-face, the edges should wind counter-clockwise + topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 2, topSlice.CutVertices.Count - 1)); + bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 1, bottomSlice.CutVertices.Count - 2)); + } + else + { + // Triangle slice above the cutting plane is a simple triangle + topSlice.AddTriangle(index13_A, index23_A, topSlice.IndexMap[v3_idx], subMesh); + + // Triangle slice below the cutting plane is a quad, so divide into two triangles + bottomSlice.AddTriangle(bottomSlice.IndexMap[v1_idx], bottomSlice.IndexMap[v2_idx], index13_B, subMesh); + bottomSlice.AddTriangle(bottomSlice.IndexMap[v2_idx], index23_B, index13_B, subMesh); + + // When looking at the cut-face, the edges should wind counter-clockwise + topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 1, topSlice.CutVertices.Count - 2)); + bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 2, bottomSlice.CutVertices.Count - 1)); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs.meta new file mode 100644 index 0000000..a5eadf3 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshSlicer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 838da7c4073cb5544a139058fcac2186 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs new file mode 100644 index 0000000..4264ea1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs @@ -0,0 +1,54 @@ +using UnityEngine; +using UnityEngine.TestTools; + +/// +/// Data structure containing position/normal/UV data for a single vertex +/// +public struct MeshVertex +{ + public Vector3 position; + public Vector3 normal; + public Vector2 uv; + + public MeshVertex(Vector3 position) + { + this.position = position; + this.normal = Vector3.zero; + this.uv = Vector2.zero; + } + + public MeshVertex(Vector3 position, Vector3 normal, Vector2 uv) + { + this.position = position; + this.normal = normal; + this.uv = uv; + } + + public override bool Equals(object obj) + { + if (!(obj is MeshVertex)) return false; + + return ((MeshVertex)obj).position.Equals(this.position); + } + + public static bool operator ==(MeshVertex lhs, MeshVertex rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(MeshVertex lhs, MeshVertex rhs) + { + return !lhs.Equals(rhs); + } + + public override int GetHashCode() + { + return this.position.GetHashCode(); + } + + [ExcludeFromCoverage] + public override string ToString() + { + return $"Position = {position}, Normal = {normal}, UV = {uv}"; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs.meta new file mode 100644 index 0000000..ede55b2 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/MeshVertex.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92df5b8640ebfb243a19d317a09dec51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs new file mode 100644 index 0000000..56d00e1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs @@ -0,0 +1,56 @@ +using UnityEngine.TestTools; + +/// +/// Data structure that holds triangulation adjacency data for a quad +/// +public struct Quad +{ + // q3 + // *---------*---------* + // \ / \ / + // \ t2L / \ t2R / + // \ / \ / + // \ / t2 \ / + // q1 *---------* q2 + // / \ t1 / \ + // / \ / \ + // / t1L \ / t1R \ + // / \ / \ + // *---------*---------* + // q4 + + /// + /// The indices of the quad vertices + /// + public int q1, q2, q3, q4; + + /// + /// The triangles that make up the quad + /// + public int t1, t2; + + /// + /// Triangle adjacency data + /// + public int t1L, t1R, t2L, t2R; + + public Quad(int q1, int q2, int q3, int q4, int t1, int t2, int t1L, int t1R, int t2L, int t2R) + { + this.q1 = q1; + this.q2 = q2; + this.q3 = q3; + this.q4 = q4; + this.t1 = t1; + this.t2 = t2; + this.t1L = t1L; + this.t1R = t1R; + this.t2L = t2L; + this.t2R = t2R; + } + + [ExcludeFromCoverage] + public override string ToString() + { + return $"T{t1}/T{t2} (V{q1},V{q2},V{q3},V{q4})"; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs.meta new file mode 100644 index 0000000..7af6a33 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Quad.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1124001d561fa9e4dbaef234fcbb0d10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs new file mode 100644 index 0000000..6afd709 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs @@ -0,0 +1,40 @@ +using UnityEngine; +using UnityEngine.TestTools; + +/// +/// This data structure is used to represent a point during triangulation. +/// +public class TriangulationPoint: IBinSortable +{ + /// + /// 2D coordinates of the point on the triangulation plane + /// + public Vector2 coords; + + /// + /// Bin used for sorting points in grid + /// + public int bin { get; set; } + + /// + /// Original index prior to sorting + /// + public int index = 0; + + /// + /// Instantiates a new triangulation point + /// + /// The index of the point in the original point list + /// The 2D coordinates of the point in the triangulation plane + public TriangulationPoint(int index, Vector2 coords) + { + this.index = index; + this.coords = coords; + } + + [ExcludeFromCoverage] + public override string ToString() + { + return $"{coords} -> {bin}"; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs.meta new file mode 100644 index 0000000..cf2f035 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/TriangulationPoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db421ee84ac7f3246925361ae77699bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs new file mode 100644 index 0000000..1d5f9fc --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs @@ -0,0 +1,599 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Logic for triangulating a set of 3D points. Only supports convex polygons. +/// +public class Triangulator +{ + // Constants for triangulation array indices + protected const int V1 = 0; // Vertex 1 + protected const int V2 = 1; // Vertex 2 + protected const int V3 = 2; // Vertex 3 + protected const int E12 = 3; // Adjacency data for edge (V1 -> V2) + protected const int E23 = 4; // Adjacency data for edge (V2 -> V3) + protected const int E31 = 5; // Adjacency data for edge (V3 -> V1) + + // Index for super triangle + protected const int SUPERTRIANGLE = 0; + + // Index for out of bounds triangle (boundary edge) + protected const int OUT_OF_BOUNDS = -1; + + // Number of points to be triangulated (excluding super triangle vertices) + protected int N; + + // Total number of triangles generated during triangulation + protected int triangleCount; + + // Triangle vertex and adjacency data + // Index 0 = Triangle index + // Index 1 = [V1, V2, V3, E12, E23, E32] + protected int[, ] triangulation; + + // Points on the plane to triangulate + public TriangulationPoint[] points; + + // Array which tracks which triangles should be ignored in the final triangulation + protected bool[] skipTriangle; + + // Normal of the plane on which the points lie + protected Vector3 normal; + + // Normalization scale factor + public float normalizationScaleFactor = 1f; + + /// + /// Initializes the triangulator with the vertex data to be triangulated + /// + /// The points to triangulate + /// The normal of the triangulation plane + public Triangulator(List inputPoints, Vector3 normal) + { + // Need at least three input vertices to triangulate + if (inputPoints == null || inputPoints.Count < 3) + { + return; + } + + this.N = inputPoints.Count; + this.triangleCount = 2 * N + 1; + this.triangulation = new int[triangleCount, 6]; + this.skipTriangle = new bool[triangleCount]; + this.points = new TriangulationPoint[N + 3]; // Extra 3 points used to store super triangle + this.normal = normal; + + // Choose two points in the plane as one basis vector + Vector3 e1 = (inputPoints[0].position - inputPoints[1].position).normalized; + Vector3 e2 = normal.normalized; + Vector3 e3 = Vector3.Cross(e1, e2).normalized; + + // To find the 2nd basis vector, find the largest component and swap with the smallest, negating the largest + + // Project 3D vertex onto the 2D plane + for (int i = 0; i < N; i++) + { + var position = inputPoints[i].position; + var coords = new Vector2(Vector3.Dot(position, e1), Vector3.Dot(position, e3)); + this.points[i] = new TriangulationPoint(i, coords); + } + } + + /// + /// Performs the triangulation + /// + /// Returns an array containing the indices of the triangles, mapped to the list of points passed in during initialization + public virtual int[] Triangulate() + { + // Need at least 3 vertices to triangulate + if (N < 3) + { + return new int[] { }; + } + + this.AddSuperTriangle(); + this.NormalizeCoordinates(); + this.ComputeTriangulation(); + this.DiscardTrianglesWithSuperTriangleVertices(); + + List triangles = new List(3 * triangleCount); + for (int i = 0; i < triangleCount; i++) + { + // Add all triangles that don't contain a super-triangle vertex + if (!skipTriangle[i]) + { + triangles.Add(triangulation[i, V1]); + triangles.Add(triangulation[i, V2]); + triangles.Add(triangulation[i, V3]); + } + } + + return triangles.ToArray(); + } + + /// + /// Uniformly scales the 2D coordinates of all the points between [0, 1] + /// + protected void NormalizeCoordinates() + { + // 1) Normalize coordinates. Coordinates are scaled so they lie between 0 and 1 + // The scaling should be uniform so relative positions of points are unchanged + + float xMin = float.MaxValue; + float xMax = float.MinValue; + float yMin = float.MaxValue; + float yMax = float.MinValue; + + // Find min/max points in the set + for (int i = 0; i < N; i++) + { + var point = points[i]; + if (point.coords.x < xMin) xMin = point.coords.x; + if (point.coords.y < yMin) yMin = point.coords.y; + if (point.coords.x > xMax) xMax = point.coords.x; + if (point.coords.y > yMax) yMax = point.coords.y; + } + + // Normalization coefficient. Using same coefficient for both x & y + // ensures uniform scaling + normalizationScaleFactor = Mathf.Max(xMax - xMin, yMax - yMin); + + // Normalize each point + for (int i = 0; i < N; i++) + { + var point = points[i]; + var normalizedPos = new Vector2( + (point.coords.x - xMin) / normalizationScaleFactor, + (point.coords.y - yMin) / normalizationScaleFactor); + + points[i].coords = normalizedPos; + } + } + + /// + /// Sorts the points into bins using an ordered grid + /// + /// Returns the array of sorted points + protected TriangulationPoint[] SortPointsIntoBins() + { + // Compute the number of bins along each axis + int n = Mathf.RoundToInt(Mathf.Pow((float) N, 0.25f)); + + // Total bin count + int binCount = n * n; + + // Assign bin numbers to each point by taking the normalized coordinates + // and dividing them into a n x n grid. + for (int k = 0; k < N; k++) + { + var point = this.points[k]; + int i = (int) (0.99f * n * point.coords.y); + int j = (int) (0.99f * n * point.coords.x); + point.bin = BinSort.GetBinNumber(i, j, n); + } + + return BinSort.Sort(this.points, N, binCount); + } + + /// + /// Computes the triangulation of the point set. + /// + /// Returns true if the triangulation was successful + protected bool ComputeTriangulation() + { + // Index of the current triangle being searched + int tSearch = 0; + // Index of the last triangle formed + int tLast = 0; + + var sortedPoints = SortPointsIntoBins(); + + // Loop through each point and insert it into the triangulation + for (int i = 0; i < N; i++) + { + TriangulationPoint point = sortedPoints[i]; + + // Insert new point into the triangulation. Start by finding the triangle that contains the point `p` + // Keep track of how many triangles we visited in case search fails and we get stuck in a loop + int counter = 0; + bool pointInserted = false; + while (!pointInserted) + { + if (counter++ > tLast || tSearch == OUT_OF_BOUNDS) + { + break; + } + + // Get coordinates of triangle vertices + var v1 = this.points[triangulation[tSearch, V1]].coords; + var v2 = this.points[triangulation[tSearch, V2]].coords; + var v3 = this.points[triangulation[tSearch, V3]].coords; + + // Verify that point is on the correct side of each edge of the triangle. + // If a point is on the left side of an edge, move to the adjacent triangle and check again. The search + // continues until a containing triangle is found or the point is outside of all triangles + if (!MathUtils.IsPointOnRightSideOfLine(v1, v2, point.coords)) + { + tSearch = triangulation[tSearch, E12]; + } + else if (!MathUtils.IsPointOnRightSideOfLine(v2, v3, point.coords)) + { + tSearch = triangulation[tSearch, E23]; + } + else if (!MathUtils.IsPointOnRightSideOfLine(v3, v1, point.coords)) + { + tSearch = triangulation[tSearch, E31]; + } + // If it is on the right side of all three edges, it is contained within the triangle (Unity uses CW winding). + else + { + InsertPointIntoTriangle(point, tSearch, tLast); + tLast += 2; + tSearch = tLast; + pointInserted = true; + } + } + } + + return true; + } + + /// + /// Initializes the triangulation by inserting the super triangle + /// + protected void AddSuperTriangle() + { + // Add new points to the end of the points array + this.points[N] = new TriangulationPoint(N, new Vector2(-100f, -100f)); + this.points[N + 1] = new TriangulationPoint(N + 1, new Vector2(0f, 100f)); + this.points[N + 2] = new TriangulationPoint(N + 2, new Vector2(100f, -100f)); + + // Store supertriangle in the first column of the vertex and adjacency data + triangulation[SUPERTRIANGLE, V1] = N; + triangulation[SUPERTRIANGLE, V2] = N + 1; + triangulation[SUPERTRIANGLE, V3] = N + 2; + + // Zeros signify boundary edges + triangulation[SUPERTRIANGLE, E12] = OUT_OF_BOUNDS; + triangulation[SUPERTRIANGLE, E23] = OUT_OF_BOUNDS; + triangulation[SUPERTRIANGLE, E31] = OUT_OF_BOUNDS; + } + + /// + /// Inserts the point `p` into triangle `t`, replacing it with three new triangles + /// + /// The index of the point to insert + /// The index of the triangle + /// Total number of triangles created so far + protected void InsertPointIntoTriangle(TriangulationPoint p, int t, int triangleCount) + { + // V1 + // * + // /|\ + // /3|2\ + // / | \ + // / | \ + // / | \ + // / | \ + // / t1 | t3 \ + // / | \ + // / 1 * 1 \ + // / __/1\__ \ + // / __/ \__ \ + // / 2__/ t2 \__3 \ + // / _/3 2\_ \ + // *---------------------------* + // V3 V2 + + int t1 = t; + int t2 = triangleCount + 1; + int t3 = triangleCount + 2; + + // Add the vertex & adjacency information for the two new triangles + // New vertex is set to first vertex of each triangle to help with + // restoring the triangulation later on + triangulation[t2, V1] = p.index; + triangulation[t2, V2] = triangulation[t, V2]; + triangulation[t2, V3] = triangulation[t, V3]; + + triangulation[t2, E12] = t3; + triangulation[t2, E23] = triangulation[t, E23]; + triangulation[t2, E31] = t1; + + triangulation[t3, V1] = p.index; + triangulation[t3, V2] = triangulation[t, V1]; + triangulation[t3, V3] = triangulation[t, V2]; + + triangulation[t3, E12] = t1; + triangulation[t3, E23] = triangulation[t, E12]; + triangulation[t3, E31] = t2; + + // Triangle index remains the same for E12, no need to update adjacency + UpdateAdjacency(triangulation[t, E12], t, t3); + UpdateAdjacency(triangulation[t, E23], t, t2); + + // Replace existing triangle `t` with `t1` + triangulation[t1, V2] = triangulation[t, V3]; + triangulation[t1, V3] = triangulation[t, V1]; + triangulation[t1, V1] = p.index; + + triangulation[t1, E23] = triangulation[t, E31]; + triangulation[t1, E12] = t2; + triangulation[t1, E31] = t3; + + // After the triangles have been inserted, restore the Delauney triangulation + RestoreDelauneyTriangulation(p, t1, t2, t3); + } + + /// + /// Restores the triangulation to a Delauney triangulation after new triangles have been added. + /// + /// Index of the inserted point + /// Index of first triangle to check + /// Index of second triangle to check + /// Index of third triangle to check + protected void RestoreDelauneyTriangulation(TriangulationPoint p, int t1, int t2, int t3) + { + int t4; + Stack < (int, int) > s = new Stack < (int, int) > (); + + s.Push((t1, triangulation[t1, E23])); + s.Push((t2, triangulation[t2, E23])); + s.Push((t3, triangulation[t3, E23])); + + while (s.Count > 0) + { + // Pop next triangle and its adjacent triangle off the stack + // t1 contains the newly added vertex at V1 + // t2 is adjacent to t1 along the opposite edge of V1 + (t1, t2) = s.Pop(); + + if (t2 == OUT_OF_BOUNDS) + { + continue; + } + // If t2 circumscribes p, the quadrilateral formed by t1+t2 has the + // diagonal drawn in the wrong direction and needs to be swapped + else if (SwapQuadDiagonalIfNeeded(p.index, t1, t2, out t3, out t4)) + { + // Push newly formed triangles onto the stack to see if their diagonals + // need to be swapped + s.Push((t1, t3)); + s.Push((t2, t4)); + } + } + } + + /// + /// Swaps the diagonal of the quadrilateral formed by triangle `t` and the + /// triangle adjacent to the edge that is opposite of the newly added point + /// + /// The index of the inserted point + /// Index of the triangle containing p + /// Index of the triangle opposite t1 that shares edge E23 with t1 + /// Index of triangle adjacent to t1 after swap + /// Index of triangle adjacent to t2 after swap + /// Returns true if the swap was performed. If the swap was not + /// performed (e.g. returns false), t3 and t4 are unused. + /// + protected bool SwapQuadDiagonalIfNeeded(int p, int t1, int t2, out int t3, out int t4) + { + // 1) Form quadrilateral from t1 + t2 (q0->q1->q2->q3) + // 2) Swap diagonal between q1->q3 to q0->q2 + // + // BEFORE AFTER + // + // q3 q3 + // *-------------*-------------* *-------------*-------------* + // \ / \ / \ /|\ / + // \ t3 / \ t4 / \ t3 /3|2\ t4 / + // \ / \ / \ / | \ / + // \ / \ / \ / | \ / + // \ / t2 \ / \ / | \ / + // \ / \ / \ / | \ / + // q1 *-------------* q2 q1 * 2 t1 | t2 3 * q2 + // \2 3/ \ | / + // \ / \ | / + // \ t1 / \ | / + // \ / \ | / + // \ / \1|1/ + // \1/ \|/ + // * q4 == p * q4 == p + // + + // Get the vertices of the quad. The new vertex is always located at V1 of the triangle + int q4 = p; + int q1, q2, q3; + + // Since t2 might be oriented in any direction, find which edge is adjacent to `t` + // The 4th vertex of the quad will be opposite this edge. We also need the two triangles + // t3 and t3 that are adjacent to t2 along the other edges since the adjacency information + // needs to be updated for those triangles. + if (triangulation[t2, E12] == t1) + { + q1 = triangulation[t2, V2]; + q2 = triangulation[t2, V1]; + q3 = triangulation[t2, V3]; + + t3 = triangulation[t2, E23]; + t4 = triangulation[t2, E31]; + } + else if (triangulation[t2, E23] == t1) + { + q1 = triangulation[t2, V3]; + q2 = triangulation[t2, V2]; + q3 = triangulation[t2, V1]; + + t3 = triangulation[t2, E31]; + t4 = triangulation[t2, E12]; + } + else // (triangulation[t2, E31] == t1) + { + q1 = triangulation[t2, V1]; + q2 = triangulation[t2, V3]; + q3 = triangulation[t2, V2]; + + t3 = triangulation[t2, E12]; + t4 = triangulation[t2, E23]; + } + + // Perform test to see if p lies in the circumcircle of t2 + if (SwapTest(points[q1].coords, points[q2].coords, points[q3].coords, points[q4].coords)) + { + // Update adjacency for triangles adjacent to t1 and t2 + UpdateAdjacency(t3, t2, t1); + UpdateAdjacency(triangulation[t1, E31], t1, t2); + + // Perform the swap. As always, put the new vertex as the first vertex of the triangle + triangulation[t1, V1] = q4; + triangulation[t1, V2] = q1; + triangulation[t1, V3] = q3; + + triangulation[t2, V1] = q4; + triangulation[t2, V2] = q3; + triangulation[t2, V3] = q2; + + // Update adjacency information (order of operations is important here since we + // are overwriting data). + triangulation[t2, E12] = t1; + triangulation[t2, E23] = t4; + triangulation[t2, E31] = triangulation[t1, E31]; + + // triangulation[t1, E12] = t2; + triangulation[t1, E23] = t3; + triangulation[t1, E31] = t2; + + return true; + } + else + { + return false; + } + } + + /// + /// Marks any triangles that contain super-triangle vertices as discarded + /// + protected void DiscardTrianglesWithSuperTriangleVertices() + { + for (int i = 0; i < triangleCount; i++) + { + // Add all triangles that don't contain a super-triangle vertex + if (TriangleContainsVertex(i, N) || + TriangleContainsVertex(i, N + 1) || + TriangleContainsVertex(i, N + 2)) + { + skipTriangle[i] = true; + } + } + } + + /// + /// Checks to see if the triangle formed by points v1->v2->v3 circumscribes point vP + /// + /// Coordinates of 1st vertex of triangle + /// Coordinates of 2nd vertex of triangle + /// Coordinates of 3rd vertex of triangle + /// Coordinates of test point + /// Returns true if the triangle `t` circumscribes the point `p` + protected bool SwapTest(Vector2 v1, Vector2 v2, Vector2 v3, Vector2 v4) + { + float x13 = v1.x - v3.x; + float x23 = v2.x - v3.x; + float y13 = v1.y - v3.y; + float y23 = v2.y - v3.y; + float x14 = v1.x - v4.x; + float x24 = v2.x - v4.x; + float y14 = v1.y - v4.y; + float y24 = v2.y - v4.y; + + float cosA = x13 * x23 + y13 * y23; + float cosB = x24 * x14 + y24 * y14; + + if (cosA >= 0 && cosB >= 0) + { + return false; + } + else if (cosA < 0 && cosB < 0) + { + return true; + } + else + { + float sinA = (x13 * y23 - x23 * y13); + float sinB = (x24 * y14 - x14 * y24); + float sinAB = sinA * cosB + sinB * cosA; + return sinAB < 0; + } + } + + /// + /// Checks if the triangle `t` contains the specified vertex + /// + /// The index of the triangle + /// The index of the vertex + /// Returns true if the triangle `t` contains the vertex `v` + protected bool TriangleContainsVertex(int t, int v) + { + return triangulation[t, V1] == v || triangulation[t, V2] == v || triangulation[t, V3] == v; + } + + /// + /// Updates the adjacency information in triangle `t`. Any references to `tOld are + /// replaced with `tNew` + /// + /// The index of the triangle to update + /// The index to be replaced + /// The new index to replace with + protected void UpdateAdjacency(int t, int tOld, int tNew) + { + // Boundary edge, no triangle exists + int sharedEdge; + if (t == OUT_OF_BOUNDS) + { + return; + } + else if (FindSharedEdge(t, tOld, out sharedEdge)) + { + triangulation[t, sharedEdge] = tNew; + } + } + + /// + /// Finds the edge index for triangle `tOrigin` that is adjacent to triangle `tAdjacent` + /// + /// The origin triangle to search + /// The triangle index to search for + /// Edge index returned as an out parameter + /// True if `tOrigin` is adjacent to `tAdjacent` and supplies the + /// shared edge index via the out parameter. If `tOrigin` is an invalid index or + /// `tAdjacent` is not adjacent to `tOrigin`, returns false. + protected bool FindSharedEdge(int tOrigin, int tAdjacent, out int edgeIndex) + { + edgeIndex = 0; + + if (tOrigin == OUT_OF_BOUNDS) + { + return false; + } + else if (triangulation[tOrigin, E12] == tAdjacent) + { + edgeIndex = E12; + return true; + } + else if (triangulation[tOrigin, E23] == tAdjacent) + { + edgeIndex = E23; + return true; + } + else if (triangulation[tOrigin, E31] == tAdjacent) + { + edgeIndex = E31; + return true; + } + else + { + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs.meta new file mode 100644 index 0000000..78dc2ee --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/Triangulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8e611cd8a0ddf64a802581a422b9733 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs new file mode 100644 index 0000000..41dda43 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs @@ -0,0 +1,83 @@ +using UnityEngine; +using UnityEngine.Events; + +public class UnfreezeFragment : MonoBehaviour +{ + [Tooltip("Options for triggering the fracture")] + public TriggerOptions triggerOptions; + + [Tooltip("If true, all sibling fragments will be unfrozen if the trigger conditions for this fragment are met.")] + public bool unfreezeAll = true; + + [Tooltip("This callback is invoked when the fracturing process has been completed.")] + public UnityEvent onFractureCompleted; + + // True if this fragment has already been unfrozen + private bool isFrozen = true; + + void OnCollisionEnter(Collision collision) + { + if (!this.isFrozen) + { + return; + } + + if (collision.contactCount > 0) + { + // Collision force must exceed the minimum force (F = I / T = F) + var contact = collision.contacts[0]; + var collisionForce = collision.impulse.magnitude / Time.fixedDeltaTime; + + // Colliding object tag must be in the set of allowed collision tags if filtering by tag is enabled + bool colliderTagAllowed = triggerOptions.IsTagAllowed(contact.otherCollider.gameObject.tag); + + // Fragment is unfrozen if the colliding object has the correct tag (if tag filtering is enabled) + // and the collision force exceeds the minimum collision force. + if (collisionForce > triggerOptions.minimumCollisionForce && + (!triggerOptions.filterCollisionsByTag || colliderTagAllowed)) + { + this.Unfreeze(); + } + } + } + + void OnTriggerEnter(Collider collider) + { + if (!this.isFrozen) + { + return; + } + + bool tagAllowed = triggerOptions.IsTagAllowed(collider.gameObject.tag); + if (!triggerOptions.filterCollisionsByTag || triggerOptions.IsTagAllowed(collider.gameObject.tag)) + { + this.Unfreeze(); + } + } + + private void Unfreeze() + { + if (this.unfreezeAll) + { + foreach(UnfreezeFragment fragment in this.transform.parent.GetComponentsInChildren()) + { + fragment.UnfreezeThis(); + } + } + else + { + UnfreezeThis(); + } + + if (this.onFractureCompleted != null) + { + this.onFractureCompleted.Invoke(); + } + } + + private void UnfreezeThis() + { + this.GetComponent().constraints = RigidbodyConstraints.None; + this.isFrozen = false; + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs.meta new file mode 100644 index 0000000..2073d18 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Fragment/UnfreezeFragment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8db8defab3610854196e7e67bb44cc26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef new file mode 100644 index 0000000..77569a9 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "RuntimeAssembly", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef.meta new file mode 100644 index 0000000..ecb76b9 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/OpenFracture.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7b1a8d9f4355a214f9a95471fa510c86 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options.meta new file mode 100644 index 0000000..db76e92 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26a59f482c449a140b34fcccb46073f4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs new file mode 100644 index 0000000..daf6fcf --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; +using UnityEngine.Events; + +[Serializable] +public class CallbackOptions +{ + [Tooltip("This callback is invoked when the fracturing/slicing process has been completed.")] + public UnityEvent onCompleted; + + public CallbackOptions() + { + this.onCompleted = null; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs.meta new file mode 100644 index 0000000..a834abe --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/CallbackOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 348edbe3d0570a6419accc52b0e212bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs new file mode 100644 index 0000000..fec7b7d --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs @@ -0,0 +1,50 @@ +using System; +using UnityEngine; + +[Serializable] +/// +/// Options for fracturing a mesh +/// +public class FractureOptions +{ + [Range(1, 1024)] + [Tooltip("Maximum number of times an object and its children are recursively fractured. Larger fragment counts will result in longer computation times.")] + public int fragmentCount; + + [Tooltip("Enables fracturing in the local X plane")] + public bool xAxis; + + [Tooltip("Enables fracturing in the local Y plane")] + public bool yAxis; + + [Tooltip("Enables fracturing in the local Z plane")] + public bool zAxis; + + [Tooltip("Enables detection of \"floating\" fragments when fracturing non-convex meshes. This setting has no effect for convex meshes and should be disabled.")] + public bool detectFloatingFragments; + + [Tooltip("Fracturing is performed asynchronously on the main thread.")] + public bool asynchronous; + + [Tooltip("The material to use for the inside faces")] + public Material insideMaterial; + + [Tooltip("Scale factor to apply to texture coordinates")] + public Vector2 textureScale; + + [Tooltip("Offset to apply to texture coordinates")] + public Vector2 textureOffset; + + public FractureOptions() + { + this.fragmentCount = 10; + this.xAxis = true; + this.yAxis = true; + this.zAxis = true; + this.detectFloatingFragments = false; + this.asynchronous = false; + this.insideMaterial = null; + this.textureScale = Vector2.one; + this.textureOffset = Vector2.zero; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs.meta new file mode 100644 index 0000000..4e4574f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/FractureOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a403b44a0def994f9876eb8ee5ec510 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs new file mode 100644 index 0000000..499a684 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs @@ -0,0 +1,25 @@ +using System; +using UnityEngine; + +[Serializable] +/// +/// Options for prefracturing a mesh +/// +public class PrefractureOptions +{ + [Tooltip("For prefractured objects, if this property is enabled, the all fragments will unfreeze if a single fragment is interacted with.")] + public bool unfreezeAll; + + [Tooltip("Saves the fragment meshes to disk. Required if the fragments will be used in a prefab.")] + public bool saveFragmentsToDisk; + + [Tooltip("Path to save the fragments to if saveToDisk is enabled. Relative to the project directory.")] + public string saveLocation; + + public PrefractureOptions() + { + this.unfreezeAll = true; + this.saveFragmentsToDisk = false; + this.saveLocation = ""; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs.meta new file mode 100644 index 0000000..6c8b525 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/PrefractureOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1288923c231259a499e20568accf3230 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs new file mode 100644 index 0000000..43632d0 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +[Serializable] +/// +/// Options for refracturing +/// +public class RefractureOptions +{ + [Tooltip("Enables refracturing of fragments. WARNING: This setting can result in a significant amount of generated fragments. It is recommended to keep FragmentCount low if this is enabled.")] + public bool enableRefracturing; + + [Tooltip("Maximum number of times a fragment can be re-fractured.")] + [Range(1, 3)] + public int maxRefractureCount; + + [Tooltip("Enable if refracturing should also invoke the callback functions.")] + public bool invokeCallbacks; + + public RefractureOptions() + { + this.enableRefracturing = false; + this.maxRefractureCount = 1; + this.invokeCallbacks = false; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs.meta new file mode 100644 index 0000000..c06fdbc --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/RefractureOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16f54522207146b4080c2b883054c223 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs new file mode 100644 index 0000000..00da2c3 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +[Serializable] +public class SliceOptions +{ + [Tooltip("Enables reslicing of fragments.")] + public bool enableReslicing; + + [Tooltip("Maximum number of times a fragment can be re-sliced.")] + [Range(1, 100)] + public int maxResliceCount; + + [Tooltip("Enables detection of \"floating\" fragments when slicing non-convex meshes. This setting has no effect for convex meshes and should be disabled.")] + public bool detectFloatingFragments; + + [Tooltip("The material to use for the inside faces")] + public Material insideMaterial; + + [Tooltip("Scale factor to apply to texture coordinates")] + public Vector2 textureScale; + + [Tooltip("Offset to apply to texture coordinates")] + public Vector2 textureOffset; + + [Tooltip("Enable if re-slicing should also invoke the callback functions.")] + public bool invokeCallbacks; + + public SliceOptions() + { + this.enableReslicing = false; + this.maxResliceCount = 1; + this.insideMaterial = null; + this.textureScale = Vector2.one; + this.textureOffset = Vector2.zero; + this.invokeCallbacks = false; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs.meta new file mode 100644 index 0000000..e04c726 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/SliceOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b00168a0f92544f468d07cb7c6ae6491 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs new file mode 100644 index 0000000..8ff411f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +public enum TriggerType +{ + Collision, + Trigger, + Keyboard +} + +[Serializable] +public class TriggerOptions +{ + [Tooltip("The type of input that triggers the fracture.")] + public TriggerType triggerType; + + [Tooltip("Minimum contact collision force required to cause the object to fracture.")] + public float minimumCollisionForce; + + [Tooltip("If true, only objects with the tags 'Allowed Tags' list will trigger a collision.")] + public bool filterCollisionsByTag; + + [Tooltip("If 'Filter Collisions By Tag' is set to true, only objects with the tags in this list will trigger the fracture.")] + public List triggerAllowedTags; + + [Tooltip("If the trigger type is Keyboard, this is the key code that will trigger a fracture when pressed.")] + public KeyCode triggerKey; + + public TriggerOptions() + { + this.triggerType = TriggerType.Collision; + this.minimumCollisionForce = 0f; + this.filterCollisionsByTag = false; + this.triggerAllowedTags = new List(); + this.triggerKey = KeyCode.None; + } + + /// + /// Returns true if the specified tag is allowed to trigger the fracture + /// + /// The tag to check + /// + public bool IsTagAllowed(string tag) + { + return triggerAllowedTags.Contains(tag); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs.meta new file mode 100644 index 0000000..551a4b1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Options/TriggerOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23860b87355c53642bbc74c4577baacf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs new file mode 100644 index 0000000..62371a2 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Events; + +[RequireComponent(typeof(MeshFilter))] +[RequireComponent(typeof(MeshRenderer))] +[RequireComponent(typeof(Rigidbody))] +public class Prefracture : MonoBehaviour +{ + public TriggerOptions triggerOptions; + public FractureOptions fractureOptions; + public CallbackOptions callbackOptions; + public PrefractureOptions prefractureOptions; + + /// + /// Collector object that stores the produced fragments + /// + private GameObject fragmentRoot; + + void OnValidate() + { + if (this.transform.parent != null) + { + // When an object is fractured, the fragments are created as children of that object's parent. + // Because of this, they inherit the parent transform. If the parent transform is not scaled + // the same in all axes, the fragments will not be rendered correctly. + var scale = this.transform.parent.localScale; + if ((scale.x != scale.y) || (scale.x != scale.z) || (scale.y != scale.z)) + { + Debug.LogWarning($"Warning: Parent transform of fractured object must be uniformly scaled in all axes or fragments will not render correctly.", this.transform); + } + } + } + + /// + /// Compute the fracture and create the fragments + /// + /// + [ExecuteInEditMode] + [ContextMenu("Prefracture")] + public void ComputeFracture() + { + // This method should only be called from the editor during design time + if (!Application.isEditor || Application.isPlaying) return; + + var mesh = this.GetComponent().sharedMesh; + + if (mesh != null) + { + // If the fragment root object has not yet been created, create it now + if (this.fragmentRoot == null) + { + // Create a game object to contain the fragments + this.fragmentRoot = new GameObject($"{this.name}Fragments"); + this.fragmentRoot.transform.SetParent(this.transform.parent); + + // Each fragment will handle its own scale + this.fragmentRoot.transform.position = this.transform.position; + this.fragmentRoot.transform.rotation = this.transform.rotation; + this.fragmentRoot.transform.localScale = Vector3.one; + } + + var fragmentTemplate = CreateFragmentTemplate(); + + Fragmenter.Fracture(this.gameObject, + this.fractureOptions, + fragmentTemplate, + this.fragmentRoot.transform, + prefractureOptions.saveFragmentsToDisk, + prefractureOptions.saveLocation); + + // Done with template, destroy it. Since we're in editor, use DestroyImmediate + GameObject.DestroyImmediate(fragmentTemplate); + + // Deactivate the original object + this.gameObject.SetActive(false); + + // Fire the completion callback + if (callbackOptions.onCompleted != null) + { + callbackOptions.onCompleted.Invoke(); + } + } + } + + /// + /// Creates a template object which each fragment will derive from + /// + /// + private GameObject CreateFragmentTemplate() + { + // If pre-fracturing, make the fragments children of this object so they can easily be unfrozen later. + // Otherwise, parent to this object's parent + GameObject obj = new GameObject(); + obj.name = "Fragment"; + obj.tag = this.tag; + + // Update mesh to the new sliced mesh + obj.AddComponent(); + + // Add renderer. Default material goes in slot 1, cut material in slot 2 + var meshRenderer = obj.AddComponent(); + meshRenderer.sharedMaterials = new Material[2] { + this.GetComponent().sharedMaterial, + this.fractureOptions.insideMaterial + }; + + // Copy collider properties to fragment + var thisCollider = this.GetComponent(); + var fragmentCollider = obj.AddComponent(); + fragmentCollider.convex = true; + fragmentCollider.sharedMaterial = thisCollider.sharedMaterial; + fragmentCollider.isTrigger = thisCollider.isTrigger; + + // Copy rigid body properties to fragment + var rigidBody = obj.AddComponent(); + // When pre-fracturing, freeze the rigid body so the fragments don't all crash to the ground when the scene starts. + rigidBody.constraints = RigidbodyConstraints.FreezeAll; + rigidBody.drag = this.GetComponent().drag; + rigidBody.angularDrag = this.GetComponent().angularDrag; + rigidBody.useGravity = this.GetComponent().useGravity; + + var unfreeze = obj.AddComponent(); + unfreeze.unfreezeAll = prefractureOptions.unfreezeAll; + unfreeze.triggerOptions = this.triggerOptions; + unfreeze.onFractureCompleted = callbackOptions.onCompleted; + + return obj; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs.meta new file mode 100644 index 0000000..23cfd2f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Prefracture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 605444303d16d4544bb76342ce272af3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs new file mode 100644 index 0000000..3bf6c50 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs @@ -0,0 +1,131 @@ +using UnityEngine; +using UnityEngine.Events; + +[RequireComponent(typeof(MeshFilter))] +[RequireComponent(typeof(MeshRenderer))] +[RequireComponent(typeof(Rigidbody))] +public class Slice : MonoBehaviour +{ + public SliceOptions sliceOptions; + public CallbackOptions callbackOptions; + + /// + /// The number of times this fragment has been re-sliced. + /// + private int currentSliceCount; + + /// + /// Collector object that stores the produced fragments + /// + private GameObject fragmentRoot; + + /// + /// Slices the attached mesh along the cut plane + /// + /// The cut plane normal vector in world coordinates. + /// The cut plane origin in world coordinates. + public void ComputeSlice(Vector3 sliceNormalWorld, Vector3 sliceOriginWorld) + { + var mesh = this.GetComponent().sharedMesh; + + if (mesh != null) + { + // If the fragment root object has not yet been created, create it now + if (this.fragmentRoot == null) + { + // Create a game object to contain the fragments + this.fragmentRoot = new GameObject($"{this.name}Slices"); + this.fragmentRoot.transform.SetParent(this.transform.parent); + + // Each fragment will handle its own scale + this.fragmentRoot.transform.position = this.transform.position; + this.fragmentRoot.transform.rotation = this.transform.rotation; + this.fragmentRoot.transform.localScale = Vector3.one; + } + + var sliceTemplate = CreateSliceTemplate(); + var sliceNormalLocal = this.transform.InverseTransformDirection(sliceNormalWorld); + var sliceOriginLocal = this.transform.InverseTransformPoint(sliceOriginWorld); + + Fragmenter.Slice(this.gameObject, + sliceNormalLocal, + sliceOriginLocal, + this.sliceOptions, + sliceTemplate, + this.fragmentRoot.transform); + + // Done with template, destroy it + GameObject.Destroy(sliceTemplate); + + // Deactivate the original object + this.gameObject.SetActive(false); + + // Fire the completion callback + if (callbackOptions.onCompleted != null) + { + callbackOptions.onCompleted.Invoke(); + } + } + } + /// + /// Creates a template object which each fragment will derive from + /// + /// + private GameObject CreateSliceTemplate() + { + // If pre-fracturing, make the fragments children of this object so they can easily be unfrozen later. + // Otherwise, parent to this object's parent + GameObject obj = new GameObject(); + obj.name = "Slice"; + obj.tag = this.tag; + + // Update mesh to the new sliced mesh + obj.AddComponent(); + + // Add materials. Normal material goes in slot 1, cut material in slot 2 + var meshRenderer = obj.AddComponent(); + meshRenderer.sharedMaterials = new Material[2] { + this.GetComponent().sharedMaterial, + this.sliceOptions.insideMaterial + }; + + // Copy collider properties to fragment + var thisCollider = this.GetComponent(); + var fragmentCollider = obj.AddComponent(); + fragmentCollider.convex = true; + fragmentCollider.sharedMaterial = thisCollider.sharedMaterial; + fragmentCollider.isTrigger = thisCollider.isTrigger; + + // Copy rigid body properties to fragment + var thisRigidBody = this.GetComponent(); + var fragmentRigidBody = obj.AddComponent(); + fragmentRigidBody.velocity = thisRigidBody.velocity; + fragmentRigidBody.angularVelocity = thisRigidBody.angularVelocity; + fragmentRigidBody.drag = thisRigidBody.drag; + fragmentRigidBody.angularDrag = thisRigidBody.angularDrag; + fragmentRigidBody.useGravity = thisRigidBody.useGravity; + + // If refracturing is enabled, create a copy of this component and add it to the template fragment object + if (this.sliceOptions.enableReslicing && + (this.currentSliceCount < this.sliceOptions.maxResliceCount)) + { + CopySliceComponent(obj); + } + + return obj; + } + + /// + /// Convenience method for copying this component to another component + /// + /// The GameObject to copy this component to + private void CopySliceComponent(GameObject obj) + { + var sliceComponent = obj.AddComponent(); + + sliceComponent.sliceOptions = this.sliceOptions; + sliceComponent.callbackOptions = this.callbackOptions; + sliceComponent.currentSliceCount = this.currentSliceCount + 1; + sliceComponent.fragmentRoot = this.fragmentRoot; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs.meta new file mode 100644 index 0000000..68cbc95 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slice.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc1ac885d6b125f44935bf90b4e492a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers.meta new file mode 100644 index 0000000..4ce5edc --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3e85072fad0cb14eb428bfc7fa67615 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs new file mode 100644 index 0000000..20c2a02 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs @@ -0,0 +1,84 @@ +using UnityEngine; +using UnityEngine.TestTools; + +[ExcludeFromCoverage] +public class PlaneSlicer : MonoBehaviour +{ + public float RotationSensitivity = 1f; + + public void OnTriggerStay(Collider collider) + { + var material = collider.gameObject.GetComponent().material; + if (material.name.StartsWith("HighlightSlice")) + { + material.SetVector("CutPlaneNormal", this.transform.up); + material.SetVector("CutPlaneOrigin", this.transform.position); + } + } + + public void OnTriggerExit(Collider collider) + { + var material = collider.gameObject.GetComponent().material; + if (material.name.StartsWith("HighlightSlice")) + { + material.SetVector("CutPlaneOrigin", Vector3.positiveInfinity); + } + } + + // Update is called once per frame + void Update() + { + if (Input.GetKey(KeyCode.Q)) + { + this.transform.Rotate(Vector3.forward, RotationSensitivity, Space.Self); + } + if (Input.GetKey(KeyCode.E)) + { + this.transform.Rotate(Vector3.forward, -RotationSensitivity, Space.Self); + } + + if (Input.GetKeyDown(KeyCode.LeftShift)) + { + var mesh = this.GetComponent().sharedMesh; + var center = mesh.bounds.center; + var extents = mesh.bounds.extents; + + var tf = this.transform; + // +-----+-----+ + // | | | + // +-----+-----+ + // | | | + // +-----+-----+ + // |- z -| + extents = new Vector3(extents.x * tf.localScale.x, + extents.y * tf.localScale.y, + extents.z * tf.localScale.z); // to World extents + + // Cast a ray and find the nearest object + RaycastHit[] hits = Physics.BoxCastAll(tf.position, extents, tf.forward, tf.rotation, extents.z); + // +-----------------+ + // / /| + // / + / | + // / / | + // +-----------------+ | + // | | | + // | | + | + // | + | + + // | | / + // | | / + // +-----------------+ + // |- 2z -| + foreach(RaycastHit hit in hits) + { + var obj = hit.collider.gameObject; + var sliceObj = obj.GetComponent(); + + if (sliceObj != null) + { + sliceObj.GetComponent()?.material.SetVector("CutPlaneOrigin", Vector3.positiveInfinity); + sliceObj.ComputeSlice(this.transform.up, this.transform.position); + } + } + } + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs.meta new file mode 100644 index 0000000..d716436 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Slicers/PlaneSlicer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d327cd6cfb3516d49ad96107a8b6d8ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support.meta new file mode 100644 index 0000000..07e5153 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5515d45c7935d204bbce3e79310fdba2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs new file mode 100644 index 0000000..a1cb947 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs @@ -0,0 +1,77 @@ +using UnityEngine; +using UnityEngine.TestTools; + +[ExcludeFromCoverage] +public class CameraController : MonoBehaviour +{ + [Tooltip("Acceleration of the player")] + public float acceleration = 100.0f; + + [Tooltip("Maximum speed of the player while walking")] + public float maxSpeed = 5.0f; + + [Tooltip("Sensitivity of the mouse for pan / tilt.")] + public float mouseSensitivity = 5.0f; + + private float startTime = 0f; + private float elapsedTime = 0f; + + void Start() + { + startTime = Time.time; + Cursor.lockState = CursorLockMode.Locked; + } + + void Update() + { + float dx = Input.GetAxis("Mouse X") * mouseSensitivity; + float dy = Input.GetAxis("Mouse Y") * mouseSensitivity; + + if (elapsedTime > 0.5f) + { + this.transform.parent.Rotate(Vector3.up, dx); + + // Clamp pitch to [-80, 80] degrees + var currentPitch = this.transform.eulerAngles.x; + if (currentPitch > 180f) currentPitch -= 360f; + var newPitch = Mathf.Clamp(currentPitch - dy, -80f, 80f); + this.transform.localEulerAngles = new Vector3(newPitch, 0, 0); + } + else + { + elapsedTime = Time.time - startTime; + } + } + + // Update is called once per frame + void FixedUpdate() + { + // Check for player movement. We can handle input here because it is continuous and + // not instantaneous like jumping. + var rigidbody = this.transform.parent.GetComponent(); + if (Input.GetKey(KeyCode.W)) + { + rigidbody.AddRelativeForce(Vector3.forward * acceleration, ForceMode.Acceleration); + } + if (Input.GetKey(KeyCode.A)) + { + rigidbody.AddRelativeForce(Vector3.left * acceleration, ForceMode.Acceleration); + } + if (Input.GetKey(KeyCode.S)) + { + rigidbody.AddRelativeForce(Vector3.back * acceleration, ForceMode.Acceleration); + } + if (Input.GetKey(KeyCode.D)) + { + rigidbody.AddRelativeForce(Vector3.right * acceleration, ForceMode.Acceleration); + } + + // Clamp the player's velocity in the X and Z directions + Vector2 xzVelocity = new Vector2(rigidbody.velocity.x, rigidbody.velocity.z); + if (xzVelocity.magnitude > maxSpeed) + { + var xzClampedVelocity = maxSpeed * xzVelocity.normalized; + rigidbody.velocity = new Vector3(xzClampedVelocity.x, rigidbody.velocity.y, xzClampedVelocity.y); + } + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs.meta new file mode 100644 index 0000000..3a28087 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/CameraController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dca2007b8cabd9747a798832c390aaba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs new file mode 100644 index 0000000..05bfb06 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs @@ -0,0 +1,26 @@ +using UnityEngine; +using UnityEngine.TestTools; + +[ExcludeFromCoverage] +public class Projectile : MonoBehaviour +{ + public GameObject projectile; + public float initialVelocity; + public KeyCode FireKey; + + // Update is called once per frame + void Update() + { + if (Input.GetKeyDown(FireKey)) + { + // Remove other projectiles from the scene + foreach(GameObject obj in GameObject.FindGameObjectsWithTag("Projectile")) + { + GameObject.Destroy(obj); + } + + var projectileInstance = GameObject.Instantiate(projectile, this.transform.position, Quaternion.identity); + projectileInstance.GetComponent().velocity = initialVelocity * this.transform.forward; + } + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs.meta new file mode 100644 index 0000000..b313757 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/Projectile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bad14770ea7af9e4baaa4dc184db69a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs new file mode 100644 index 0000000..3464a65 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +public class ToggleText : MonoBehaviour +{ + public KeyCode toggleKey; + + public GameObject textObject; + + // Start is called before the first frame update + void Update() + { + if (Input.GetKeyDown(toggleKey)) + { + textObject.SetActive(!textObject.activeSelf); + } + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs.meta new file mode 100644 index 0000000..947da4b --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/ToggleText.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f4acb25588adc34a9d8453173077e89 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs new file mode 100644 index 0000000..a1a471c --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +public class UniqueMaterial : MonoBehaviour +{ + // Start is called before the first frame update + void Start() + { + // Creates a unique instance of the material, decoupling it from the other objects. + // This script is only used for the Slice demo to highlight slices and is not essential + // for the fracturing/slicing code to work. + this.GetComponent().material = this.GetComponent().material; + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs.meta new file mode 100644 index 0000000..e9abd3f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Support/UniqueMaterial.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4fe57792e36ffc489b628556d1dc576 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities.meta new file mode 100644 index 0000000..0864ab0 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 565bd121a6124b54f97b6266e15ab13a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs new file mode 100644 index 0000000..b963b59 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs @@ -0,0 +1,96 @@ +/// +/// Defines an interface for an object that is sorted by bin number +/// +public interface IBinSortable +{ + int bin { get; set; } +} + +/// +/// Methods for sorting objects on an ordered grid by bin number. +/// +/// The grid ordering is shown by example below. Even rows (row 0 = bottom row) are ordered +/// right-to-left while odd rows are ordered left-to-right. +/// _____ _____ _____ +/// | | | | +/// | 6 | 7 | 8 | +/// |_____|_____|_____| +/// | | | | +/// | 5 | 4 | 3 | +/// |_____|_____|_____| +/// | | | | +/// | 0 | 1 | 2 | +/// |_____|_____|_____| +/// +/// +public class BinSort +{ + /// + /// Computes the bin number for the set of grid coordinates + /// + /// Grid row + /// Grid column + /// Grid size + /// + internal static int GetBinNumber(int i, int j, int n) + { + return (i % 2 == 0) ? (i * n) + j : (i + 1) * n - j - 1; + } + + /// + /// Performs a counting sort of the input points based on their bin number. Only + /// sorts the elements in the index range [0, count]. If binCount is <= 1, no sorting + /// is performed. If lastIndex > input.Length, the entire input array is sorted. + /// + /// The input array to sort + /// The index of the last element in `input` to sort. Only the + /// elements [0, lastIndex) are sorted. + /// Number of bins + internal static T[] Sort(T[] input, int lastIndex, int binCount) where T: IBinSortable + { + int[] count = new int[binCount]; + T[] output = new T[input.Length]; + + #region Validation + // Need at least two bins to sort + if (binCount <= 1) + { + return input; + } + + // If lastIndex is out of range, default to sorting the entire input array + if (lastIndex > input.Length) + { + lastIndex = input.Length; + } + #endregion + + // Only sort the first [0, count] points, don't want to sort super-triangle vertices + for (int i = 0; i < lastIndex; i++) + { + int j = input[i].bin; + count[j] += 1; + } + + for (int i = 1; i < binCount; i++) + { + count[i] += count[i - 1]; + } + + for (int i = lastIndex - 1; i >= 0; i--) + { + int j = input[i].bin; + count[j] -= 1; + output[count[j]] = input[i]; + } + + // Copy over the rest of the un-sorted points + for (int i = lastIndex; i < output.Length; i++) + { + output[i] = input[i]; + } + + return output; + } + +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs.meta new file mode 100644 index 0000000..fdffe9f --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/BinSort.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa6520366fb0c0d418e02cb5b731ad3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs new file mode 100644 index 0000000..4c35e66 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs @@ -0,0 +1,128 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public static class MathUtils +{ + /// + /// Returns true if the quad specified by the two diagonals a1->a2 and b1->b2 is convex + /// Quad is convex if a1->a2 and b1->b2 intersect each other + /// + /// Start point of diagonal A + /// End point of diagonal A + /// Start point of diagonal B + /// End point of diagonal B + /// + public static bool IsQuadConvex(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2) + { + return LinesIntersectInternal(a1, a2, b1, b2, true); + } + + /// + /// Returns true lines a1->a2 and b1->b2 is intersect + /// + /// Start point of line A + /// End point of line A + /// Start point of line B + /// End point of line B + /// + public static bool LinesIntersect(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2) + { + return LinesIntersectInternal(a1, a2, b1, b2, false); + } + + /// + /// Returns true lines a1->a2 and b1->b2 is intersect + /// + /// Start point of line A + /// End point of line A + /// Start point of line B + /// End point of line B + /// + private static bool LinesIntersectInternal(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, bool includeSharedEndpoints) + { + Vector2 a12 = new Vector2(a2.x - a1.x, a2.y - a1.y); + Vector2 b12 = new Vector2(b2.x - b1.x, b2.y - b1.y); + + // If any of the vertices are shared between the two diagonals, + // the quad collapses into a triangle and is convex by default. + if (a1 == b1 || a1 == b2 || a2 == b1 || a2 == b2) + { + return includeSharedEndpoints; + } + else + { + // Compute cross product between each point and the opposite diagonal + // Look at sign of the Z component to see which side of line point is on + float a1xb = (a1.x - b1.x) * b12.y - (a1.y - b1.y) * b12.x; + float a2xb = (a2.x - b1.x) * b12.y - (a2.y - b1.y) * b12.x; + float b1xa = (b1.x - a1.x) * a12.y - (b1.y - a1.y) * a12.x; + float b2xa = (b2.x - a1.x) * a12.y - (b2.y - a1.y) * a12.x; + + // Check that the points for each diagonal lie on opposite sides of the other + // diagonal. Quad is also convex if a1/a2 lie on b1->b2 (and vice versa) since + // the shape collapses into a triangle (hence >= instead of >) + return ((a1xb >= 0 && a2xb <= 0) || (a1xb <= 0 && a2xb >= 0)) && + ((b1xa >= 0 && b2xa <= 0) || (b1xa <= 0 && b2xa >= 0)); + } + } + + /// + /// Determines the intersection between the line segment a->b and the plane defined by the specified normal and origin point. If an intersection point exists, it is returned via the out parameter `intersection`. The parameter `s` is defined below and is used to properly interpolate normals/uvs for intersection vertices. + /// + /// Start point of line + /// End point of line + /// Plane normal + /// Plane origin + /// If intersection exists, intersection point return as out parameter. + /// Returns the parameterization of the intersection where x = a + (b - a) * s + /// + public static bool LinePlaneIntersection(Vector3 a, + Vector3 b, + Vector3 n, + Vector3 p0, + out Vector3 x, + out float s) + { + // Initialize out params + s = 0; + x = Vector3.zero; + + // Handle degenerate cases + if (a == b) + { + return false; + } + else if (n == Vector3.zero) + { + return false; + } + + // `s` is the parameter for the line segment a -> b where 0.0 <= s <= 1.0 + s = Vector3.Dot(p0 - a, n) / Vector3.Dot(b - a, n); + + if (s >= 0 && s <= 1) + { + x = a + (b - a) * s; + return true; + } + + return false; + } + + /// + /// Returns true of the point `p` is on the left side of the directed line segment `i` -> `j` + /// Use for checking if a point is inside of a triangle. Since triangle vertices oriented + /// CCW, a point on the left side of a triangle edge is "inside" that edge of the triangle. + /// + /// Index of test point in `points` array + /// Index of first vertex of the edge in the `points` array + /// /// Index of second vertex of the edge in the `points` array + /// True if the point `p` is on the left side of the line `i`->`j` + public static bool IsPointOnRightSideOfLine(Vector2 a, Vector2 b, Vector2 c) + { + // The <= is essential; if it is <, the whole thing falls apart + return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0; + } + +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs.meta new file mode 100644 index 0000000..e752c86 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MathUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d37a4c4b4b021b4dafc4db72c4a6fc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs new file mode 100644 index 0000000..a2229af --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs @@ -0,0 +1,239 @@ +using System.Collections.Generic; +using UnityEngine; +using Unity.Collections; +using UnityEngine.Rendering; + +public static class MeshUtils +{ + // Description of vertex attributes for the island mesh + private static VertexAttributeDescriptor[] layout = new[] + { + new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), + new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), + new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2), + }; + + /// + /// Identifies all disconnected sets of geometry contained within the mesh. + /// Each set of geometry is split into a separate meshes. + /// + /// The mesh to search + /// Returns an array of all disconnected meshes found. + public static Mesh[] FindDisconnectedMeshes(Mesh mesh) + { + // Each disconnected set of geometry is referred to as an "island" + List islands = new List(); + + #region Preliminaries + + // Extract mesh data + var vertices = mesh.vertices; + var triangles = mesh.triangles; + var normals = mesh.normals; + var uvs = mesh.uv; + + // For each triangle, find the corresponding sub-mesh index. (Mesh.triangles contains + // the triangles for all sub-meshes) + int[] triangleSubMesh = new int[triangles.Length / 3]; + int subMeshIndex = 0; + int subMeshSize = mesh.GetTriangles(subMeshIndex).Length / 3; + for (int i = 0; i < triangles.Length / 3; i++) + { + if (i >= subMeshSize) + { + subMeshIndex++; + subMeshSize += mesh.GetTriangles(subMeshIndex).Length / 3; + } + triangleSubMesh[i] = subMeshIndex; + } + + // Identify coincident vertices + List[] coincidentVertices = new List[vertices.Length]; + for(int i = 0; i < vertices.Length; i++) + { + coincidentVertices[i] = new List(); + } + for(int i = 0; i < vertices.Length; i++) + { + Vector3 v_i = vertices[i]; + for (int k = i + 1; k < vertices.Length; k++) + { + Vector3 v_k = vertices[k]; + if (v_i == v_k) + { + coincidentVertices[k].Add(i); + coincidentVertices[i].Add(k); + } + } + } + + // Find the triangles the each vertex belongs to. Need to do this for each submesh + List[] vertexTriangles = new List[vertices.Length]; + + for (int i = 0; i < vertices.Length; i++) + { + vertexTriangles[i] = new List(); + } + + int v1, v2, v3; + for (int i = 0; i < triangles.Length; i += 3) + { + // Index of the triangle + int t = i / 3; + + v1 = triangles[i]; + v2 = triangles[i + 1]; + v3 = triangles[i + 2]; + + vertexTriangles[v1].Add(t); + vertexTriangles[v2].Add(t); + vertexTriangles[v3].Add(t); + } + + #endregion + + // Search the mesh geometry and identify all islands + // 1) Start by finding a vertex that has not yet been visited + // 2) Insert the vertex into a queue, begin a breadth-first search + // 3) Dequeue the next vertex 'v' + // 4) Find all triangles that 'v' is connected to. Add each triangle to a list + // 5) Enqueue the vertices for each connected triangle if they haven't been visited yet + // 6) Enqueue all vertices coincident with 'v' if they haven't been visited yet + // 7) Repeat Steps 3-6 until the queue is empty + // 8) Take the list of triangles and use the existing mesh data to create a new island mesh + // 9) Go back to Step 1, continue until all vertices have been visited. + + bool[] visitedVertices = new bool[vertices.Length]; + bool[] visitedTriangles = new bool[triangles.Length]; + Queue frontier = new Queue(); + + // Vertex data for the island mesh. Only initialize once and keep track of pointer to last element to minimize GC + NativeArray islandVertices = new NativeArray(vertices.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + // Array containing triangle data for the island mesh. Need to keep track of triangles for each sub-mesh separately + int[][] islandTriangles = new int[mesh.subMeshCount][]; + for (int i = 0; i < mesh.subMeshCount; i++) + { + islandTriangles[i] = new int[triangles.Length]; + } + + // Counters to keep track of how many vertices + int vertexCount = 0; + int totalIndexCount = 0; + int[] subMeshIndexCounts = new int[mesh.subMeshCount]; + + for (int i = 0; i < vertices.Length; i++) + { + if (visitedVertices[i]) continue; + + // Reset the vertex/triangle counts + vertexCount = 0; + totalIndexCount = 0; + for(int j = 0; j < mesh.subMeshCount; j++) + { + subMeshIndexCounts[j] = 0; + } + + // Search the mesh geometry starting at vertex 'i'. Search is performed by looking up + // the triangles that contain each vertex, adding their vertices, etc. until all + // triangles have been visited. + frontier.Enqueue(i); + + // Index map between source mesh vertex array and the sub mesh vertex arrays + int[] vertexMap = new int[vertices.Length]; + // Initialize map to '-1' to serve as "unmapped" value + for(int j = 0; j < vertices.Length; j++) + { + vertexMap[j] = -1; + } + + while (frontier.Count > 0) + { + int k = frontier.Dequeue(); + + // Ignore vertex if we've already visited it + if (visitedVertices[k]) + { + continue; + } + else + { + visitedVertices[k] = true; + } + + // Add this vertex array for the island mesh + // Map between the original vertex index to the vertex's new index in the island + // mesh vertex array. This will be used to update the indices for the triangles later + vertexMap[k] = vertexCount; + islandVertices[vertexCount++] = new MeshVertex(vertices[k], normals[k], uvs[k]); + + // Get the list of all triangles that this vertex is a part of + foreach(int t in vertexTriangles[k]) + { + // If triangle is already included, skip it + if (!visitedTriangles[t]) + { + visitedTriangles[t] = true; + + // Loop through each vertex of the triangle and add the non-visited ones + // to the search frontier + for (int m = t * 3; m < t * 3 + 3; m++) + { + int v = triangles[m]; + subMeshIndex = triangleSubMesh[t]; + islandTriangles[subMeshIndex][subMeshIndexCounts[subMeshIndex]++] = v; + totalIndexCount++; + + frontier.Enqueue(v); + + // If this vertex is coincident with other vertices, add those to the search frontier + foreach(int cv in coincidentVertices[v]) + { + frontier.Enqueue(cv); + } + } + } + } + } + + // If the island contains at least one triangle, create a new mesh + if (vertexCount > 0) + { + Mesh island = new Mesh(); + + island.SetIndexBufferParams(totalIndexCount, IndexFormat.UInt32); + island.SetVertexBufferParams(vertexCount, layout); + island.SetVertexBufferData(islandVertices, 0, 0, vertexCount); + + // Set the triangles for each submesh + island.subMeshCount = mesh.subMeshCount; + int indexStart = 0; + for (subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) + { + var subMeshIndexBuffer = islandTriangles[subMeshIndex]; + var subMeshIndexCount = subMeshIndexCounts[subMeshIndex]; + + // Map vertex indexes from the original mesh to the island mesh + for(int k = 0; k < subMeshIndexCount; k++) + { + int originalIndex = subMeshIndexBuffer[k]; + subMeshIndexBuffer[k] = vertexMap[originalIndex]; + } + + // Set the index data for this sub mesh + island.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, (int)subMeshIndexCount); + island.SetSubMesh(subMeshIndex, new SubMeshDescriptor(indexStart, subMeshIndexCount)); + + indexStart += subMeshIndexCount; + } + + island.RecalculateBounds(); + + islands.Add(island); + } + } + + // Loop through rest of triangles + return islands.ToArray(); + } +} diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs.meta new file mode 100644 index 0000000..820cbd1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/MeshUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe28a9177c0175f479eb9155cc4aa3a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs new file mode 100644 index 0000000..24a44d1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; + +public static class Vector3Extensions +{ + // + // that the normal is pointing to + // - p: The point being checked + // - n: The normal of the plane + // - o: The origin of the plane + /// + /// Returns true if the point is either on or above the plane. "Above" is the side of the place in the direction of the normal. + /// + /// The test point + /// The plane normal + /// The plane origin + /// + public static bool IsAbovePlane(this Vector3 p, Vector3 n, Vector3 o) + { + return (n.x * (p.x - o.x) + n.y * (p.y - o.y) + n.z * (p.z - o.z)) >= 0; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs.meta b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs.meta new file mode 100644 index 0000000..a20e6ec --- /dev/null +++ b/Assets/Scripts/Gameplay/Fracture/Runtime/Scripts/Utilities/Vector3Extensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0cb4a40ec03ec24485fca5a7cd3ad16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 074c9f7d24c3a02c38c34f1052d9e18a771d3b0b Mon Sep 17 00:00:00 2001 From: jukrb0x <15688641+jukrb0x@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:54:17 +0800 Subject: [PATCH 10/10] test close door event calling --- Assets/Scenes/Levels/Platform.unity | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Assets/Scenes/Levels/Platform.unity b/Assets/Scenes/Levels/Platform.unity index 76316b5..963953a 100644 --- a/Assets/Scenes/Levels/Platform.unity +++ b/Assets/Scenes/Levels/Platform.unity @@ -1430,20 +1430,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: dcb81d28944043aaab3befb91e9428ca, type: 3} m_Name: m_EditorClassIdentifier: - fractureOptions: - fragmentCount: 10 - xAxis: 1 - yAxis: 1 - zAxis: 1 - detectFloatingFragments: 0 - asynchronous: 0 - insideMaterial: {fileID: 0} - textureScale: {x: 1, y: 1} - textureOffset: {x: 0, y: 0} - callbackOptions: - onCompleted: - m_PersistentCalls: - m_Calls: [] --- !u!1 &937309977 GameObject: m_ObjectHideFlags: 0 @@ -4664,6 +4650,11 @@ PrefabInstance: propertyPath: onTriggerOn.m_PersistentCalls.m_Calls.Array.data[1].m_MethodName value: CloseDoor objectReference: {fileID: 0} + - target: {fileID: 2823304615256995810, guid: 82f1f40f15c734a4a9292ec51ce8ad21, + type: 3} + propertyPath: onTriggerOff.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: CloseDoor + objectReference: {fileID: 0} - target: {fileID: 2823304615256995810, guid: 82f1f40f15c734a4a9292ec51ce8ad21, type: 3} propertyPath: onTriggerOn.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName @@ -4674,6 +4665,11 @@ PrefabInstance: propertyPath: onTriggerOn.m_PersistentCalls.m_Calls.Array.data[1].m_TargetAssemblyTypeName value: Gameplay.Rig.DoorController, Assembly-CSharp objectReference: {fileID: 0} + - target: {fileID: 2823304615256995810, guid: 82f1f40f15c734a4a9292ec51ce8ad21, + type: 3} + propertyPath: onTriggerOff.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: Gameplay.Rig.DoorController, Assembly-CSharp + objectReference: {fileID: 0} - target: {fileID: 2823304615256995810, guid: 82f1f40f15c734a4a9292ec51ce8ad21, type: 3} propertyPath: onTriggerOn.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName @@ -4684,6 +4680,11 @@ PrefabInstance: propertyPath: onTriggerOn.m_PersistentCalls.m_Calls.Array.data[1].m_Arguments.m_ObjectArgumentAssemblyTypeName value: UnityEngine.Object, UnityEngine objectReference: {fileID: 0} + - target: {fileID: 2823304615256995810, guid: 82f1f40f15c734a4a9292ec51ce8ad21, + type: 3} + propertyPath: onTriggerOff.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 5476909276159794244, guid: 82f1f40f15c734a4a9292ec51ce8ad21, type: 3} propertyPath: m_RootOrder