{"id":599,"date":"2016-11-20T18:00:27","date_gmt":"2016-11-20T17:00:27","guid":{"rendered":"http:\/\/piflik.de\/?p=599"},"modified":"2016-11-09T22:37:58","modified_gmt":"2016-11-09T21:37:58","slug":"utilities-trajectory","status":"publish","type":"post","link":"http:\/\/piflik.de\/?p=599","title":{"rendered":"Utilities: Trajectory"},"content":{"rendered":"<p>One of the more recent additions to my <a title=\"Utilities\" href=\"https:\/\/bitbucket.org\/Piflik\/utilities\/src\" target=\"_blank\">Utilities<\/a>. I wrote this to calculate a parabolic trajectory for a thrown object (in this special case a grenade). More specifically, I calculate the required throw-velocity to accurately hit a specified point from a starting position and a given angle. This velocity can be capped and when the required speed exceeds this cap, the trajectory will not reach the target. The class can also calculate points on the trajectory in order to visualize them. Lastly, it provides a coroutine that can be used to make an object follow this trajectory. The calculation for this is done via a synchronized Leapfrog integration, and does not use Unity&#8217;s physics engine. Both this coroutine and the calculation for the trajectory-points can be specified to &#8216;bounce&#8217; on surfaces, where it will take into account both the bounciness of the surface that is being hit and an optional bounciness value for the object. A bouncing object will only come to rest on a surface that is sufficiently flat.<\/p>\n<p><!--more--><\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&lt;pre&gt;using System.Collections;\r\nusing UnityEngine;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing UnityEngine.Events;\r\n\r\n\/\/\/ &lt;summary&gt;\r\n\/\/\/ Calculates a parabolic trajectory from a start point to a target point and allows an object to follow this trajectory\r\n\/\/\/ Does not use Unity's physics engine, but bounces off colliders. If the collider contains a PhysicMaterial, its bounciness will be taken into account\r\n\/\/\/ &lt;\/summary&gt;\r\npublic class Trajectory {\r\n\r\n\tprivate const float GRAVITY = 10;\r\n\tprivate const float DEFAULT_TIMESTEP = 0.01f;\r\n\tprivate const float FLAT_SURFACE = 0.9f; \/\/TODO define flat surface better?\r\n\r\n\tprivate Vector3 _startPosition;\r\n\tprivate Vector3 _startVelocity;\r\n\r\n\tprivate float _bounciness;\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Calculates the required starting velocity to hit the target point from the starting position\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=&quot;start&quot;&gt;Start point of the trajectory&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;end&quot;&gt;End point of the trajectory&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;angle&quot;&gt;Angle in radians&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;maxSpeed&quot;&gt;Speed cap; if the required speed exceeds this, the trajectory will not reach the target&lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\tprivate Vector3 StartVelocity(Vector3 start, Vector3 end, float angle, float maxSpeed = Mathf.Infinity) {\r\n\t\tVector3 direction = end - start;\r\n\t\tfloat distance = direction.magnitude;\r\n\t\tfloat elevation = direction.y;\r\n\t\tdirection = direction.normalized;\r\n\r\n\t\tfloat tan = Mathf.Tan(angle);\r\n\r\n\t\tfloat speed = Mathf.Sqrt(Mathf.Abs(GRAVITY * distance * (tan * tan + 1) \/ (2 * tan - 2 * elevation \/ distance)));\r\n\t\tspeed = Mathf.Min(maxSpeed, speed);\r\n\r\n\t\treturn speed * (Mathf.Cos(angle) * direction + Mathf.Sin(angle) * Vector3.up);\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Create Vector3 array to visualize the trajectory\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=&quot;bounce&quot;&gt;&lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\tpublic IEnumerable&lt;Vector3&gt; GetTrajectoryPoints(bool bounce) {\r\n\r\n\t\tList&lt;Vector3&gt; points = new List&lt;Vector3&gt;();\r\n\r\n\t\tVector3 position = _startPosition;\r\n\t\tVector3 velocity = _startVelocity;\r\n\r\n\t\tpoints.Add(position);\r\n\r\n\t\tfloat timer = 0;\r\n\t\tint counter = 0;\r\n\t\twhile (timer &lt; 50) {\r\n\t\t\ttimer += DEFAULT_TIMESTEP;\r\n\t\t\tcounter++;\r\n\r\n\t\t\tRaycastHit hit;\r\n\t\t\tif (NextStep(ref position, ref velocity, DEFAULT_TIMESTEP, out hit)) {\r\n\r\n\t\t\t\tbool flatSurface = Vector3.Dot(hit.normal, Vector3.up) &gt; FLAT_SURFACE;\r\n\r\n\t\t\t\t\/\/points.Add(position);\r\n\r\n\t\t\t\tif(!bounce || Mathf.Approximately(velocity.y, 0) &amp;&amp; flatSurface) {\r\n\t\t\t\t\tpoints.Add(position);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (counter == 5) {\r\n\t\t\t\tpoints.Add(position);\r\n\t\t\t\tcounter = 0;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn points;\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ Updates the position and velocity to the next timestpe (synchronized leap-frog integration)\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=&quot;position&quot;&gt;Position&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;velocity&quot;&gt;Velocity&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;timeStep&quot;&gt;Timestep&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;hit&quot;&gt;If there was a collision within this timestep, this will contain the information&lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;True, if collision during timestep&lt;\/returns&gt;\r\n\tprivate bool NextStep(ref Vector3 position, ref Vector3 velocity, float timeStep, out RaycastHit hit) {\r\n\t\tbool bounced = false;\r\n\t\tVector3 nextPos = position + timeStep * velocity - 0.5f * timeStep * timeStep * GRAVITY * Vector3.up;\r\n\t\tvelocity.y += -GRAVITY * timeStep;\r\n\r\n\t\tif (Physics.Linecast(position, nextPos, out hit, -1, QueryTriggerInteraction.Ignore)) {\r\n\t\t\tnextPos = hit.point;\r\n\r\n\t\t\tPhysicMaterial physMat;\r\n\t\t\tfloat collisionDecay = (physMat = hit.collider.material).bounciness &gt; 0 ? physMat.bounciness * _bounciness : _bounciness;\r\n\t\t\tvelocity = collisionDecay * Vector3.Reflect(velocity, hit.normal);\r\n\r\n\t\t\tbounced = true;\r\n\t\t}\r\n\r\n\t\tposition = nextPos;\r\n\r\n\t\treturn bounced;\r\n\t}\r\n\r\n\t\/\/\/ &lt;summary&gt;\r\n\t\/\/\/ A coroutine to follow the trajectory\r\n\t\/\/\/ &lt;\/summary&gt;\r\n\t\/\/\/ &lt;param name=&quot;obj&quot;&gt;Object to follow this trajectory&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;follow&quot;&gt;Rotate object to face move-direction?&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;bounce&quot;&gt;Does this object bounce off surfaces?&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;onBounce&quot;&gt;optional action to perform on bounce&lt;\/param&gt;\r\n\t\/\/\/ &lt;param name=&quot;onDestinationReached&quot;&gt;optional action to perform when the object reaches the destination&lt;\/param&gt;\r\n\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\tpublic IEnumerator FollowTrajectory(Transform obj, bool follow, bool bounce, UnityAction&lt;GameObject&gt; onBounce = null, UnityAction&lt;GameObject&gt; onDestinationReached = null) {\r\n\t\tVector3 position = _startPosition;\r\n\t\tVector3 velocity = _startVelocity;\r\n\r\n\t\tfloat timer = 0;\r\n\t\twhile(timer &lt; 50) {\r\n\t\t\ttimer += Time.deltaTime;\r\n\r\n\t\t\tRaycastHit hit;\r\n\t\t\tif(NextStep(ref position, ref velocity, Time.deltaTime, out hit)) {\r\n\r\n\t\t\t\tif (onBounce != null) {\r\n\t\t\t\t\tonBounce.Invoke(obj.gameObject);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tbool flatSurface = Vector3.Dot(hit.normal, Vector3.up) &gt; FLAT_SURFACE;\r\n\r\n\t\t\t\tif(!bounce || Mathf.Abs(velocity.y) &lt; 0.1f &amp;&amp; flatSurface) {\r\n\t\t\t\t\tobj.position = position;\r\n\t\t\t\t\tif (onDestinationReached != null) {\r\n\t\t\t\t\t\tonDestinationReached.Invoke(obj.gameObject);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tobj.position = position;\r\n\r\n\t\t\tif (follow) {\r\n\t\t\t\tobj.rotation = Quaternion.LookRotation(velocity.normalized, obj.up);\r\n\t\t\t}\r\n\r\n\t\t\tyield return null;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic Trajectory (Vector3 start, Vector3 end, float throwAngle, float maxSpeed, bool bounce, float bounciness = 1) {\r\n\t\t_bounciness = bounciness;\r\n\t\t_startPosition = start;\r\n\t\t_startVelocity = StartVelocity(start, end, throwAngle, maxSpeed);\r\n\t}\r\n\r\n\tpublic void Update(Vector3 start, Vector3 end, float throwAngle, float maxSpeed, bool bounce, float bounciness = 1) {\r\n\t\t_bounciness = bounciness;\r\n\t\t_startPosition = start;\r\n\t\t_startVelocity = StartVelocity(start, end, throwAngle, maxSpeed);\r\n\t}\r\n\r\n\tprivate Trajectory() {}\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>One of the more recent additions to my Utilities. I wrote this to calculate a parabolic trajectory for a thrown object (in this special case a grenade). More specifically, I calculate the required throw-velocity to accurately hit a specified point from a starting position and a given angle. This velocity can be capped and when [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[13,12],"tags":[],"_links":{"self":[{"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/posts\/599"}],"collection":[{"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/piflik.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=599"}],"version-history":[{"count":2,"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/posts\/599\/revisions"}],"predecessor-version":[{"id":601,"href":"http:\/\/piflik.de\/index.php?rest_route=\/wp\/v2\/posts\/599\/revisions\/601"}],"wp:attachment":[{"href":"http:\/\/piflik.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=599"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/piflik.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=599"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/piflik.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=599"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}