using System.Collections; using System.Collections.Generic; using UnityEngine; using Obi; [RequireComponent(typeof(ObiRope))] public class RopeSweepCut : MonoBehaviour { public Camera cam; ObiRope rope; LineRenderer lineRenderer; Vector3 cutStartPosition; Vector3 cutEndPosition; bool cut; private void Awake() { rope = GetComponent(); AddMouseLine(); } private void OnDestroy() { DeleteMouseLine(); } private void OnEnable() { rope.OnSimulationStart += Rope_OnBeginSimulation; } private void OnDisable() { rope.OnSimulationStart -= Rope_OnBeginSimulation; } private void AddMouseLine() { GameObject line = new GameObject("Mouse Line"); lineRenderer = line.AddComponent(); lineRenderer.startWidth = 0.005f; lineRenderer.endWidth = 0.005f; lineRenderer.numCapVertices = 2; lineRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Color")); lineRenderer.sharedMaterial.color = Color.cyan; lineRenderer.enabled = false; } private void DeleteMouseLine() { if (lineRenderer != null) Destroy(lineRenderer.gameObject); } private void LateUpdate() { // do nothing if we don't have a camera to cut from. if (cam == null) return; // process user input and cut the rope if necessary. ProcessInput(); } private void Rope_OnBeginSimulation(ObiActor actor, float stepTime, float substepTime) { if (cut) { ScreenSpaceCut(cutStartPosition, cutEndPosition); cut = false; } } /** * Very simple mouse-based input. Not ideal for multitouch screens as it only supports one finger, though. */ private void ProcessInput() { // When the user clicks the mouse, start a line cut: if (Input.GetMouseButtonDown(0)) { cutStartPosition = Input.mousePosition; lineRenderer.SetPosition(0, cam.ScreenToWorldPoint(new Vector3(cutStartPosition.x, cutStartPosition.y, 0.5f))); lineRenderer.enabled = true; } if (lineRenderer.enabled) lineRenderer.SetPosition(1, cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.5f))); // When the user lifts the mouse, proceed to cut. if (Input.GetMouseButtonUp(0)) { cutEndPosition = Input.mousePosition; lineRenderer.enabled = false; cut = true; } } /** * Cuts the rope using a line segment, expressed in screen-space. */ private void ScreenSpaceCut(Vector2 lineStart, Vector2 lineEnd) { // keep track of whether the rope was cut or not. bool ropeCut = false; // iterate over all elements and test them for intersection with the line: for (int i = 0; i < rope.elements.Count; ++i) { // project the both ends of the element to screen space. Vector3 screenPos1 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle1]); Vector3 screenPos2 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle2]); // test if there's an intersection: if (SegmentSegmentIntersection(screenPos1, screenPos2, lineStart, lineEnd, out float r, out float s)) { ropeCut = true; rope.Tear(rope.elements[i]); } } // If the rope was cut at any point, rebuilt constraints: if (ropeCut) rope.RebuildConstraintsFromElements(); } /** * line segment 1 is AB = A+r(B-A) * line segment 2 is CD = C+s(D-C) * if they intesect, then A+r(B-A) = C+s(D-C), solving for r and s gives the formula below. * If both r and s are in the 0,1 range, it meant the segments intersect. */ private bool SegmentSegmentIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D, out float r, out float s) { float denom = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x); float rNum = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y); float sNum = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y); if (Mathf.Approximately(rNum, 0) || Mathf.Approximately(denom, 0)) { r = -1; s = -1; return false; } r = rNum / denom; s = sNum / denom; return (r >= 0 && r <= 1 && s >= 0 && s <= 1); } }