public SimpleTransform Scaled(Vector3 scale)
        {
            var ret = new SimpleTransform(this);

            ret.Scale(scale);
            return(ret);
        }
 public static SimpleTransform Lerp(SimpleTransform a, SimpleTransform b, float percent)
 {
     return(new SimpleTransform(
                Vector3.Lerp(a.position, b.position, percent),
                Quaternion.Slerp(a.rotation, b.rotation, percent)
                ));
 }
        private void Straighten()
        {
            var track = Track;

            Undo.RecordObjects(TrackEditor.GetObjectsInvolvedWithTrack(track), "Straighten");
            var length = track.Length;

            track.curveEndStrength = track.curveStartStrength = Track.DefaultCurveStrength;

            if (workDirection > 0)
            {
                var newEnd = new SimpleTransform(new Vector3(0, 0, length));
                track.TrackEnd = newEnd;
                if (track.NextTrack)
                {
                    track.NextTrack.TrackAbsoluteStart = track.TrackAbsoluteEnd;
                }
            }
            else
            {
                var absEnd = track.TrackAbsoluteEnd;
                track.TrackAbsoluteStart = new SimpleTransform(
                    absEnd.position + absEnd.rotation * new Vector3(0, 0, -length),
                    absEnd.rotation
                    );

                if (track.PrevTrack)
                {
                    track.PrevTrack.TrackAbsoluteEnd = track.TrackAbsoluteStart;
                }
            }

            track.Update();
        }
 /**
  * Moves our start or end (or target's start or end) to the target's/our end/start.
  * If snapMe is true, moves me, if false moves the other piece.
  * If snapEnd is true, our end is moving/being moved, if false our start is moving/being moved.
  *
  * Also automatically links the tracks.
  */
 public void SnapTogether(Track target, bool snapMe, bool snapEnd)
 {
     if (snapEnd)
     {
         if (snapMe)
         {
             TrackAbsoluteEnd = target.TrackAbsoluteStart;
         }
         else
         {
             target.TrackAbsoluteStart = TrackAbsoluteEnd;
         }
         NextTrack        = target;
         target.PrevTrack = this;
     }
     else
     {
         if (snapMe)
         {
             TrackAbsoluteStart = target.TrackAbsoluteEnd;
         }
         else
         {
             target.TrackAbsoluteEnd = TrackAbsoluteStart;
         }
         PrevTrack        = target;
         target.NextTrack = this;
     }
 }
Beispiel #5
0
        protected void BlockMovement(SimpleTransform trackTangent, Vector3 trackEnd)
        {
            if (!endJoint)
            {
                endJoint = gameObject.AddComponent <ConfigurableJoint>();
                setAllAxies(endJoint, ConfigurableJointMotion.Free);
                endJoint.zMotion       = ConfigurableJointMotion.Limited;
                endJoint.connectedBody = trackRB;
                endJoint.axis          = Vector3.right;
                endJoint.secondaryAxis = Vector3.up;

                endJoint.autoConfigureConnectedAnchor = false;
                endJoint.anchor = Vector3.zero;        //center of cart

                //Set the limit so we will "hit the end" when we our center is clearingDistance away from the track end.
                //(and we'll place the joint's "center" 2*clearingDistance away from the end)
                var l = endJoint.linearLimit;
                l.limit = clearingDistance;
                endJoint.linearLimit = l;
            }

            // The anchor should be 2*clearingDistance away from the end
            var anchorDistance = clearingDistance * 2;
            var anchorPos      = trackEnd + trackTangent.backward * anchorDistance;

            // connectedAnchor is in local coordinates for the track/world
            endJoint.connectedAnchor = trackRB ? trackRB.transform.InverseTransformPoint(anchorPos) : anchorPos;
        }
Beispiel #6
0
        /**
         * There is where it all happens.
         *
         * Here's how the track constraint works:
         * (Getting this to work in Unity was blood, sweat, and couple of tears, what's describe below it how we got it to work.)
         *
         * Each cart has a "guide". The guide is attached to the track (via fixed* joint). The guide is, each frame, teleported
         * to the nearest "correct" position on the track and reaffixed there.
         *
         * Connected to the guide is a slider joint**, which allows free movement along one axis. The cart slides along this
         * axis, but the joint, and therefore axis, are moved by the guide.
         *
         * That, combined with a dash of magic and some prayer seems to be enough to get it working in Unity.
         *
         * (Note: Unity does not seem to correctly support changing most the parameters on a configurable joint while the game is running,
         * including changing the axis of a slider joint, among other things.)
         *
         * (*Actually, it's a configurable joint with specific settings that gives us better results than a regular fixed joint.)
         * (**A fixed joint with all the axes but one locked.)
         *
         * This is not an perfect solution, a native joint type would be better, but it's what we can muster.
         *
         * (Unfortunately, this physics method does not take into account torque: for example, a cart on
         * a "screw" track won't necessarily go forward if you twist it.)
         */
        public void FixedUpdate()
        {
            if (brakesCooldown > 0)
            {
                --brakesCooldown;
            }

            if (!_currentTrack)
            {
                return;
            }
            if (!guide)
            {
                SetupGuide();
            }

            SimpleTransform t;

            UpdateTrackSlider(out t);
            LastTangent = t;
            PreserveCurve(LastTangent);
            float forwardness;

            PushThings(LastTangent, out forwardness);
            UpdateEndStop(LastTangent, forwardness);
        }
        public static SimpleTransform operator*(SimpleTransform a, SimpleTransform b)
        {
            SimpleTransform ret = new SimpleTransform();

            ret.rotation = a.rotation * b.rotation;
            ret.position = a.position + a.rotation * b.position;
            return(ret);
        }
Beispiel #8
0
 /**
  * Returns the world velocity of the track at the given point.
  */
 protected Vector3 GetTrackVelocityAt(SimpleTransform trackTangent)
 {
     if (!trackRB)
     {
         return(Vector3.zero);
     }
     return(trackRB.GetPointVelocity(trackTangent.position));
 }
        public void OnInspectorUpdate()
        {
            var track = Track;

            if (track && lastEnd != track.TrackEnd)
            {
                lastEnd = track.TrackEnd;
                Regen();
            }
        }
        /** If a view has been selected to be aligned, this will align it with the current selection. */
        public void AlignView()
        {
            if (GUIUtility.hotControl != 0)
            {
                //don't move views while dragging, wait for them to finish
                if (!deferredActions.Contains(AlignView))
                {
                    Defer(AlignView);
                }
                return;
            }

            var track = Track;

            if (!track)
            {
                return;
            }
            if (PrefabUtility.GetPrefabType(track) == PrefabType.Prefab)
            {
                return;
            }

            if (alignViewIdx > 0 && alignViewIdx <= SceneView.sceneViews.Count)
            {
                //Here we use the "undocumented, unsupported" SceneView API.
                var view = GetSceneViews()[alignViewIdx - 1];

                var transform       = track.transform;
                var oldPos          = new SimpleTransform(transform);
                var absStart        = track.TrackAbsoluteStart;
                var absEnd          = track.TrackAbsoluteEnd;
                var hoverAwayVector = (Vector3.up + Vector3.back * 3);

                //move transform so we can align the view (and then revert it)
                if (workDirection > 0)
                {
                    transform.LookAt(absEnd.position, absStart.up);
                    transform.position += transform.rotation * hoverAwayVector;
                }
                else
                {
                    absEnd.AboutFaced().ApplyTo(transform);            //move to end looking at start

                    transform.LookAt(absStart.position, absEnd.up);
                    transform.position += transform.rotation * hoverAwayVector;
                }

                view.AlignViewToObject(transform);
                oldPos.ApplyTo(transform);
            }
        }
 /** Returns true if the two transforms are close enough to be considered "aligned" and allow cars to pass across them. */
 public static bool IsAligned(SimpleTransform a, SimpleTransform b)
 {
     if (Vector3.Distance(a.position, b.position) > connectTolerance)
     {
         return(false);
     }
     if (Quaternion.Angle(a.rotation, b.rotation) > connectAngleTolerance)
     {
         return(false);
     }
     //Debug.Log("Found tolerable: " + Vector3.Distance(a.position, b.position) + " apart, " + Quaternion.Angle(a.rotation, b.rotation) + "deg off base");
     return(true);
 }
Beispiel #12
0
        public TrackCurve(CurveType curveType, SimpleTransform end, float startStrength, float endStrength)
        {
            this.curveType     = curveType;
            this.end           = end;
            this.startStrength = startStrength;
            this.endStrength   = endStrength;

            if (Vector3.Distance(Vector3.zero, end.position) < 0.000001f)
            {
                throw new System.ArgumentOutOfRangeException("end", "This curve is a point. Move the start and end apart.");
            }

            CalculateData();
        }
        /**
         * Finds all the nearest tracks and links them together.
         * Not fast. Only modifies this track, if this breaks links on other tracks, you will need to relink them too.
         */
        public static void RelinkTrack(Track track)
        {
            if (!track)
            {
                return;
            }

            bool matchedNext = false, matchedPrev = false;


            SimpleTransform myStart = new SimpleTransform(track.transform), myEnd = track.TrackAbsoluteEnd;

            foreach (Track other in Object.FindObjectsOfType(typeof(Track)))
            {
                if (other == track)
                {
                    continue;
                }
                SimpleTransform otherStart = new SimpleTransform(other.transform), otherEnd = other.TrackAbsoluteEnd;

                //join everything that can be joined
                if (Track.IsAligned(myStart, otherStart) || Track.IsAligned(myStart, otherEnd))
                {
                    matchedPrev = true;
                    if (track.PrevTrack != other)
                    {
                        track.PrevTrack = other;
                        Debug.Log("Linked " + other.name + " to before " + track.name);
                    }
                }
                if (Track.IsAligned(myEnd, otherStart) || Track.IsAligned(myEnd, otherEnd))
                {
                    matchedNext = true;
                    if (track.NextTrack != other)
                    {
                        track.NextTrack = other;
                        Debug.Log("Linked " + other.name + " to after " + track.name);
                    }
                }
            }

            if (!matchedNext)
            {
                track.NextTrack = null;
            }
            if (!matchedPrev)
            {
                track.PrevTrack = null;
            }
        }
Beispiel #14
0
        /**
         * Does some bits to work around the fact that we aren't a first-class physics constraint.
         */
        protected void PreserveCurve(SimpleTransform trackTangent)
        {
            //Instead of relying wholly on ERP to turn the cart on a curve, let's transfer the velocity to the new angle.
            //Because PhysX lies to us about speed when the track is moving, we can't use this when the track has a rigidbody.
            if (trackRB || cartRB.IsSleeping())
            {
                return;
            }

            var cartVel       = cartRB.velocity;
            var newDir        = trackTangent.rotation * Vector3.forward;
            var movingForward = Mathf.Sign(Vector3.Dot(cartVel, newDir));
            var newVel        = movingForward * cartVel.magnitude * newDir;

            cartRB.velocity = Vector3.Lerp(cartVel, newVel, curvePreservation);
        }
        /** Starts switching to the given position. */
        public void Switch(int index)
        {
            lastPosition    = endSwitching ? track.TrackAbsoluteEnd : track.TrackAbsoluteStart;
            desiredPosition = index;
            switchStartTime = Time.time;
            switching       = true;

            foreach (var position in positions)
            {
                if (endSwitching)
                {
                    position.PrevTrack = null;
                }
                else
                {
                    position.NextTrack = null;
                }
            }
        }
Beispiel #16
0
        /**
         * The constraints do strange things, like give us the wrong speed while on a moving piece of track.
         *
         * When we get off the track, call this and we'll fix it.
         */
        protected void RepairVelocity(SimpleTransform trackTangent)
        {
            if (!trackRB)
            {
                return;
            }

            var vel = cartRB.velocity;

            //Find the component of our motion that doesn't align with the track and eliminate it.
            //Often, this works out to be "kick the cart so it's traveling the same speed as the track."
            var relVel = cartRB.velocity - GetTrackVelocityAt(trackTangent);
            var error  = relVel - Vector3.Project(relVel, trackTangent.rotation * Vector3.forward);

            vel -= error;

            //Can't set velocity right now, things will still interact with it, do it in just a moment.
            StartCoroutine(DeferredSetVelocity(vel));
        }
Beispiel #17
0
        protected void UpdateEndStop(SimpleTransform trackTangent, float forwardness)
        {
            if (!_currentTrack || _currentTrack.outOfTrack != Track.EndOfLineBehavior.EndStop)
            {
                if (endJoint)
                {
                    Destroy(endJoint);
                    endJoint = null;
                }
                return;
            }



            if (
                //moving forward
                forwardness > 0 &&
                //no next track
                !_currentTrack.NextTrack &&
                //within 2 * clearingDistance of the end
                Vector3.Distance(_currentTrack.TrackAbsoluteEnd.position, transform.position) < clearingDistance * 2
                )
            {
                BlockMovement(trackTangent, _currentTrack.TrackAbsoluteEnd.position);
            }
            else if (
                //moving backward
                forwardness < 0 &&
                //no prev track
                !_currentTrack.PrevTrack &&
                //within 2 * clearingDistance of the start
                Vector3.Distance(_currentTrack.TrackAbsoluteStart.position, transform.position) < clearingDistance * 2
                )
            {
                BlockMovement(trackTangent.Clone().AboutFace(), _currentTrack.TrackAbsoluteStart.position);
            }
            else if (endJoint)
            {
                Destroy(endJoint);
                endJoint = null;
            }
        }
        public void FixedUpdate()
        {
            if (!switching)
            {
                return;
            }

            var percent = (Time.time - switchStartTime) / switchSpeed;

            if (switchSpeed == 0 || percent >= 1)
            {
                if (endSwitching)
                {
                    track.ConnectTo(positions[desiredPosition]);
                }
                else
                {
                    track.SnapTogether(positions[desiredPosition], true, false);
                }
                switching = false;
            }
            else
            {
                if (endSwitching)
                {
                    track.TrackAbsoluteEnd = SimpleTransform.Lerp(
                        lastPosition,
                        positions[desiredPosition].TrackAbsoluteStart,
                        percent
                        );
                }
                else
                {
                    track.TrackAbsoluteStart = SimpleTransform.Lerp(
                        lastPosition,
                        positions[desiredPosition].TrackAbsoluteEnd,
                        percent
                        );
                }
            }
        }
Beispiel #19
0
        /**
         * Returns our velocity on the track, relative to the track, but in the world coordinate system.
         *
         * This will account for errors in the physics system where a cert's velocity doesn't fully include
         * the track's velocity.
         */
        public Vector3 GetVelocityOnTrack(SimpleTransform trackTangent = null)
        {
            if (!_currentTrack)
            {
                return(Vector3.zero);
            }
            if (trackTangent == null)
            {
                trackTangent = LastTangent;
            }
            if (trackTangent == null)
            {
                return(Vector3.zero);
            }


            var worldVel = cartRB.velocity;

            var vel = worldVel - GetTrackVelocityAt(trackTangent);

            //Ideally, vel would have the same (or opposite) direction as trackTangent.rotation * Vector3.forward
            //In practice, it can be way off when the track is moving because the physics system lies about cartRB.velocity
            //Cope:
            vel = Vector3.Project(vel, trackTangent.rotation * Vector3.forward);

            if (DebugDrawSpeed)
            {
                Debug.DrawLine(transform.position, transform.position + GetTrackVelocityAt(trackTangent), Color.blue);
                if (trackRB)
                {
                    Debug.DrawLine(trackRB.transform.position, trackRB.transform.position + trackRB.velocity, Color.yellow);
                }
                Debug.DrawLine(transform.position, transform.position + worldVel, Color.magenta);
                Debug.DrawLine(transform.position, transform.position + vel, Color.red);
            }

            return(vel);
        }
 public SimpleTransform(SimpleTransform other)
 {
     position = other.position;
     rotation = other.rotation;
 }
        protected void RenderEditHandles(Track track)
        {
            var involvedTracks = GetObjectsInvolvedWithTrack(track);

            Undo.RecordObjects(involvedTracks, "Bend Track");

            const float handleSize    = .35f;
            const float strengthScale = .25f;

            SimpleTransform t, tOrig;

            if (showTrackStartHandle)
            {
                //Handle rendering and inputs on the start transform of this node
                t     = track.TrackAbsoluteStart.MakeValid();
                tOrig = new SimpleTransform(t);

                t.rotation = Handles.RotationHandle(t.rotation, t.position);
                t.position = Handles.PositionHandle(t.position, t.rotation);
                t.MakeValid();

                if (t != tOrig)
                {
                    track.TrackAbsoluteStart = t;

                    //The adjacent track is "normally" linked, bring it with us.
                    if (track.PrevTrack && track.PrevTrack.NextTrack == track)
                    {
                        track.PrevTrack.TrackAbsoluteEnd = t;
                    }
                }

                //Handle rendering and inputs of the strength of this node
                var scale = track.Distance * strengthScale;
                var pos   = t.position + t.forward * scale * track.curveStartStrength;
                Handles.color = curveColor;
                Handles.DrawLine(pos, t.position);
                Handles.color = strengthHandleColor;
                var updatedPos = Handles.Slider(
                    pos, t.forward, HandleUtility.GetHandleSize(t.position) * 2,
                    ScaleDrawSize(handleSize * .5f, Handles.SphereHandleCap), 0
                    );
                if (updatedPos != pos)
                {
                    track.curveStartStrength = Mathf.Max(0, Vector3.Dot(updatedPos - t.position, t.forward) / scale);
                }
            }
            else
            {
                //render "click to edit" button
                var p = track.TrackAbsoluteStart.MakeValid();
                var s = HandleUtility.GetHandleSize(p.position) * handleSize;
                Handles.color = Color.red;
                var clicked = Handles.Button(p.position, p.rotation, s, s, Handles.CylinderHandleCap);
                if (clicked)
                {
                    showTrackStartHandle = true;
                }
            }


            if (showTrackEndHandle)
            {
                //Handle rendering and inputs on the end transform of this node
                t     = track.TrackAbsoluteEnd.MakeValid();
                tOrig = new SimpleTransform(t);

                t.rotation = Handles.RotationHandle(t.rotation, t.position);
                t.position = Handles.PositionHandle(t.position, t.rotation);
                t.MakeValid();

                if (t != tOrig)
                {
                    track.TrackAbsoluteEnd = t;

                    //The adjacent track is "normally" linked, bring it with us.
                    if (track.NextTrack && track.NextTrack.PrevTrack == track)
                    {
                        track.NextTrack.TrackAbsoluteStart = t;
                    }
                }

                //Handle rendering and inputs of the strength of this node
                var scale = track.Distance * strengthScale;
                var pos   = t.position + t.backward * scale * track.curveEndStrength;
                Handles.color = curveColor;
                Handles.DrawLine(pos, t.position);
                Handles.color = strengthHandleColor;
                var updatedPos = Handles.Slider(
                    pos, t.backward, HandleUtility.GetHandleSize(t.position) * 2,
                    ScaleDrawSize(handleSize * .5f, Handles.SphereHandleCap), 0
                    );
                if (updatedPos != pos)
                {
                    track.curveEndStrength = Mathf.Max(0, Vector3.Dot(updatedPos - t.position, t.backward) / scale);
                }
            }
            else
            {
                //render "click to edit" button
                var p = track.TrackAbsoluteEnd.MakeValid();        //Unity's definition of "Unit quaternion" is unnecessarily strict.
                var s = HandleUtility.GetHandleSize(p.position) * handleSize;
                Handles.color = Color.green;
                var clicked = Handles.Button(p.position, p.rotation, s, s, Handles.CylinderHandleCap);
                if (clicked)
                {
                    showTrackEndHandle = true;
                }
            }

            //Draw navigation/add buttons
            System.Action <Track, bool> doNavButton = (adjTrack, start) => {
                var p  = (start ? track.TrackAbsoluteStart : track.TrackAbsoluteEnd).MakeValid();
                var s  = HandleUtility.GetHandleSize(p.position) * handleSize;
                var s2 = s * .7f;
                Handles.color = adjTrack ? new Color(0, 0, 1, .5f) : Color.green;

                //var direction = start ? p.Clone().AboutFace().rotation : p.rotation;
                var direction = p.rotation;
                var dirSign   = start ? -1 : 1;

                var pos     = p.position + (p.rotation * new Vector3(0, 0, dirSign * s * 4));
                var clicked = Handles.Button(pos, direction, s2, s2, Handles.ConeHandleCap);

                if (clicked && adjTrack)
                {
                    Selection.activeGameObject = adjTrack.gameObject;
                }
                else if (clicked)
                {
                    var newTrack = AddTrack(track, start);
                    Undo.RegisterCreatedObjectUndo(newTrack.gameObject, "Create Track");
                    Selection.activeGameObject = newTrack.gameObject;
                }
            };

            doNavButton(track.PrevTrack, true);
            doNavButton(track.NextTrack, false);



            if (GUI.changed)
            {
                MarkDirty(involvedTracks);
            }
        }
        public SimpleTransform AboutFaced()
        {
            var ret = new SimpleTransform(this);

            return(ret.AboutFace());
        }
Beispiel #23
0
        protected void PushThings(SimpleTransform trackTangent, out float forwardness)
        {
            //forwardness measures which direction we are moving relative to the tracks's forward direction
            //at the current point
            forwardness = 0;

            if (!_currentTrack)
            {
                return;
            }

            //apply acceleration/deceleration as per the track's spec

            var velocity = GetVelocityOnTrack(trackTangent);

            forwardness = Vector3.Dot(velocity, trackTangent.rotation * Vector3.forward);
            //Track actions are based on track direction, not ours.
            if (cartReversed)
            {
                forwardness *= -1;
            }

            float speed = velocity.magnitude;

            F_msds getOptimumForce = (currentSpeed, optimumSpeed, mass) => {
                //f = m*a
                //s[t] = a*t + s[0] -> (s[t] - s[0]) / t = a
                //f = m * (s[t] - s[0]) / t
                float deltaV = optimumSpeed - currentSpeed;
                return(mass * deltaV / Time.deltaTime);
            };

            if (_currentTrack.brakes == null)
            {
                Debug.LogWarning("No brakes object " + _currentTrack.name);
            }

            var brakesDirection   = _currentTrack.brakes.IsActive(forwardness);
            var accelDirection    = _currentTrack.acceleration.IsActive(forwardness);
            var brakesHoldingBack = false;

            if (brakesDirection != 0 && speed > _currentTrack.brakes.targetSpeed)
            {
                //slow down
                float force = -_currentTrack.brakes.maxForce;
                if (prorateForces)
                {
                    float bestForce = getOptimumForce(speed, _currentTrack.brakes.targetSpeed, cartRB.mass);
                    //Debug.Log("Best braking force: " + bestForce + " max breaking force " + force);
                    if (bestForce > force)              //(force and bestForce should be negative, hence greater than)
                    {
                        force = bestForce;
                        //it's hard to push something to hold still, so when we get to where we aren't pushing as hard as possible
                        //we'll consider it all-but-stopped and clamp on the joint
                        brakesHoldingBack = true;
                    }
                }

                //Only apply force of we can enact brakes. When an object hits a stopped cart, {brakeJoint} takes the hit and
                //absorbs energy. If we also apply our forces in that same tick we'll hold too firmly.
                if (brakesCooldown == 0)
                {
                    AddForceFromTrack(velocity.normalized * force);
                }
            }
            else if (accelDirection != 0 && speed < _currentTrack.acceleration.targetSpeed)
            {
                //push faster in this direction
                float force = _currentTrack.acceleration.maxForce;
                if (prorateForces)
                {
                    float bestForce = getOptimumForce(speed, _currentTrack.acceleration.targetSpeed, cartRB.mass);
                    //Debug.Log("Best accel force: " + bestForce + " max accel force " + force);
                    if (bestForce < force)
                    {
                        force = bestForce;
                    }
                }

                var pushDirection = trackTangent.rotation * Vector3.forward * accelDirection;
                if (cartReversed)
                {
                    pushDirection = -pushDirection;
                }
                //Debug.Log("push " + name + " " + pushDirection * force);
                AddForceFromTrack(pushDirection * force);
            }

            //If the brakes are trying to stop completely, lock in position when we stop
            const float stopThreshold = .006f;

            if (
                brakesCooldown == 0 &&
                brakesDirection != 0 &&
                _currentTrack.brakes.targetSpeed == 0
                )
            {
                if ((brakesHoldingBack || Mathf.Abs(forwardness) < stopThreshold) && !brakeJoint)
                {
                    brakeJoint = gameObject.AddComponent <FixedJoint>();
                    brakeJoint.connectedBody = _currentTrack.GetComponentInParent <Rigidbody>();
                    brakeJoint.breakForce    = _currentTrack.brakes.maxForce;
                }
            }
            else if (brakeJoint)
            {
                Destroy(brakeJoint);
                brakeJoint = null;
            }
        }
Beispiel #24
0
        protected void UpdateTrackSlider(out SimpleTransform trackTangent)
        {
            //number of times we've "recursed" (emulated with a goto)
            int iterations = 0;

            Transform cartTransform = transform;
//		Transform cartTransform = guide.transform;

            //Estimate where we'll be in about a frame or so - this allows us to "lead"
            //the constraint's position and minimize error
            Vector3         tickMovement = cartTransform.GetComponent <Rigidbody>().velocity *leadFactor *Time.deltaTime;
            SimpleTransform carSoonPos   = new SimpleTransform(cartTransform);

            carSoonPos.position += tickMovement;
            if (DebugDrawTracking)
            {
                Debug.DrawRay(carSoonPos.position, Vector3.up, Color.yellow);
                Debug.DrawRay(cartTransform.position, Vector3.up, Color.white);
            }

            //DEBUG("now: " << m_rbB.getWorldTransform().position << " mv: " << tickMovement << " predicted: " << (carSoonPos.position + tickMovement));

            //goto label used to emulate a stateful tail-end recursion
            //todo: cars shouldn't be jumping far enough to need tail-end recursion, switch to a closure
calculate:
            Track track = _currentTrack;

            //Position of cart
            var   localCartPos = track.transform.InverseTransformPoint(carSoonPos.position);
            float pos          = track.Curve.GetFraction(localCartPos);


            if (pos < 0)
            {
                //before the start
                //DEBUG("Before the start");
                if (track.PrevTrack && iterations == 0)
                {
                    //Move to the next track and run as that track
                    RollToNextTrack(false);
                    ++iterations;
                    goto calculate;
                }
                else if (track.PrevTrack)
                {
                    //we've hit the iteration limit, just go straight until next frame
                    pos = 0;
                }
                else
                {
                    //We're past the start with no previous.

                    if (_currentTrack.outOfTrack == Track.EndOfLineBehavior.FallOffTrack)
                    {
                        //wait until it's clear of the track (so it doesn't intersect with it), then drop it off
                        //(note that we use the cart's actual instead of projected position here)
                        if (Vector3.Distance(track.transform.position, cartTransform.position) >= clearingDistance)
                        {
                            RemoveFromTrack();
                        }
                    }
                    else if (_currentTrack.outOfTrack == Track.EndOfLineBehavior.ContinueStraight)
                    {
                        //do nothing special here, just keep going
                    }

                    // In all cases, set our guide to line up with the start of the track.
                    pos = 0;
                }
            }
            else if (pos > 1)
            {
                //past the end
                //DEBUG("After the end");
                if (track.NextTrack && iterations == 0)
                {
                    //Move to the next track and run as that track
                    RollToNextTrack(true);
                    ++iterations;
                    goto calculate;
                }
                else if (track.NextTrack)
                {
                    //we've hit the iteration limit, just go straight until next frame
                    pos = 1;
                }
                else
                {
                    //We're past the end with no next.

                    if (_currentTrack.outOfTrack == Track.EndOfLineBehavior.FallOffTrack)
                    {
                        //wait until it's clear of the track (so it doesn't intersect with it), then drop it off
                        if (Vector3.Distance(track.TrackAbsoluteEnd.position, cartTransform.position) >= clearingDistance)
                        {
                            RemoveFromTrack();
                        }
                    }
                    else if (_currentTrack.outOfTrack == Track.EndOfLineBehavior.ContinueStraight)
                    {
                        //do nothing special here, just keep going
                    }

                    // In all cases, set our guide to line up with the end of the track.
                    pos = 1;
                }
            }
            else
            {
                //calculate position normally
            }

            if (DebugDrawTracking)
            {
                var col = new Color(0, 0, 0, .3f);
                Debug.DrawLine(cartTransform.position, track.TrackAbsoluteEnd.position, col);
                Debug.DrawLine(cartTransform.position, track.transform.position, col);
            }
            //Debug.Log("d1: " + d1  + ", d2: " + d2 + " maxDist: " + maxDist);


            //Get position and direction at this point on the track
            trackTangent = track.Curve.GetPointAt(pos).LocalToAbsolute(track.transform);

            if (cartReversed)
            {
                trackTangent.AboutFace();
            }

            if (DebugDrawTracking && _currentTrack)
            {
                //(these two should be more-or-less on top of each other, if there's a big divergence we have an issue)
                Debug.DrawRay(guide.transform.position, guide.transform.rotation * Vector3.forward, Color.red);
                Debug.DrawRay(guide.transform.position, guide.transform.rotation * Vector3.up, Color.blue);

                Debug.DrawRay(trackTangent.position, trackTangent.rotation * Vector3.forward, Color.red);
                Debug.DrawRay(trackTangent.position, trackTangent.rotation * Vector3.up, Color.blue);
            }


            //We tell the joint what rotation we want by feeding it two (unit) vectors.
            if (trackJoint)
            {
                //to trick Unity into recalculating the targeted internal transformations, we'll switch the rigidbody on the joint
                Rigidbody differentRb = guideJoint.connectedBody ? null : GetComponent <Rigidbody>();
                Rigidbody originalRb  = guideJoint.connectedBody;

                //switch rb so we can change things without fear
                guideJoint.connectedBody = differentRb;

                //move the guide to where we want it to be now
                guide.transform.position = trackTangent.position;
                guide.transform.rotation = trackTangent.rotation;

                //put the rb back as it should, causing Unity to recalculate the desired position for the joint
                guideJoint.connectedBody = originalRb;
            }
        }
Beispiel #25
0
        /**
         * Calculates some information about the path and caches it for later.
         */
        protected void CalculateData(int steps = -1)
        {
            //Ideally, we should compute a set of "key" points (more points on curves) to use, but for now we just approximate.
            _length = 0;

            if (steps <= 0)
            {
                steps = DistanceQuality;
            }
            pathData = new PathPoint[steps + 1];

            pathData[0].pos      = new SimpleTransform();
            pathData[0].fraction = 0;
            pathData[0].distance = 0;

            /*
             * To track the upward direction along the curve, we repeatedly project the previous up vector along a number
             * of points along the spline, then determine the resulting "up direction" error and apply linearly apply a
             * correction rotation to each intermediate node.
             */

            //For each sample point, record information.
            var pos    = pathData[0].pos;
            var lastUp = Vector3.up;

            _length = 0;
            for (int i = 1; i <= steps; i++)
            {
                pathData[i].fraction = i / (float)steps;

                var moment = GetMoment(pathData[i].fraction);

                _length += (pos.position - moment.pos).magnitude;
                pathData[i].distance = _length;

                //Determine (approx.) which way is up by updating the last up vector against the current forward vector
                var rotation = Quaternion.LookRotation(moment.direction, lastUp);

                pathData[i].pos = pos = new SimpleTransform(moment.pos, rotation);

                lastUp = rotation * Vector3.up;
            }

            //Now that we have a rough idea of which way is up at each point, refine it so we end with the correct curve.
            var lastRotation     = pathData[steps].pos.rotation;
            var expectedRotation = end.rotation;
            //lastRotation and expectedRotation should both rotate a Vector.forward to the same value, but
            //most likely, they will point a Vector3.up different directions.
            //Let's find out how far off they are.
            //Rotate last into expected's frame, then decompose to Euler angles.
            var errorAngles = (Quaternion.Inverse(expectedRotation) * lastRotation).eulerAngles;

            //errorAngles.x and y should be about 0 or 360. Our error is in errorAngles.z
            var error = errorAngles.z > 180 ? errorAngles.z - 360 : errorAngles.z;

            //adjust each step to remove the error
            for (int i = 1; i < steps; i++)
            {
                var counterRotate = -error * pathData[i].distance / _length;

                pathData[i].pos.rotation = Quaternion.AngleAxis(counterRotate, pathData[i].pos.rotation * Vector3.forward) * pathData[i].pos.rotation;
            }

            //The last item should always fit perfectly
            pathData[steps].pos = end;
        }
        public void GenerateMesh(Mesh mesh, Track track)
        {
            //This function is optimized.
            //There are some places where simpler, more fluent language constructs are avoided in the interest of performance.

            if (railMesh == null && capMesh == null && !tieMesh)
            {
                //no results case
                mesh.Clear();
                return;
            }


            //build the rails (rails are built by bridging each set of points we render)
            List <Vector3> allVerts   = new List <Vector3>();
            List <Vector3> allNormals = new List <Vector3>();
            List <Vector2> allUVs     = new List <Vector2>();
            List <int>     allTris    = new List <int>();

            if (railMesh != null)
            {
                //Rail mesh should consist of a straight piece of track along z from -.5 to .5.
                int vertOffset = allVerts.Count;

                SimpleTransform lastStep = null;
                var             steps    = GetSteps(track);

                var vertCount = (steps.Length - 1) * railMesh.verts.Count;
                var triCount  = (steps.Length - 1) * railMesh.tris.Count;
                allVerts.EnsureSpace(vertCount);
                allNormals.EnsureSpace(vertCount);
                allUVs.EnsureSpace(vertCount);
                allTris.EnsureSpace(triCount);

                foreach (float step in steps)
                {
                    //Note: we could use Curve.GetIntervals, but it's better to have more steps along curves
                    //which tends to happen (a little bit) automatically if we use the mathematical fraction.
                    SimpleTransform curStep = track.Curve.GetPointAt(step);

                    if (lastStep != null)
                    {
                        //bridge between two steps
                        int pInitial = allVerts.Count;
                        int j        = 0;
                        foreach (Vertex vert in railMesh.verts)
                        {
                            Vector3 pos = vert.pos;
                            if (pos.z < 0)
                            {
                                //this vert falls on the prev side
                                pos.z += .5f;                        //the model has data one unit wide, offset that
                                Vector3 p = lastStep * pos;
                                allVerts.Add(p);
                                allNormals.Add(lastStep.rotation * vert.normal);
                                allUVs.Add(vert.uv);
                            }
                            else
                            {
                                //this vert falls on the next side
                                pos.z -= .5f;                        //the model has data one unit wide, offset that
                                Vector3 p = curStep * pos;
                                allVerts.Add(p);
                                allNormals.Add(curStep.rotation * vert.normal);
                                allUVs.Add(vert.uv);
                            }

                            j++;
                        }

                        foreach (int idx in railMesh.tris)
                        {
                            allTris.Add(idx + pInitial + vertOffset);
                        }
                    }

                    lastStep = curStep;
                }
            }

            if (capMesh != null && (!track.NextTrack || !track.PrevTrack || track.forceEndCaps))
            {
                //Cap mesh should consist of faces in +z or -z
                //No faces may cross 0

                List <Vector3> verts   = new List <Vector3>();
                List <Vector3> normals = new List <Vector3>();
                List <Vector2> uvs     = new List <Vector2>();
                List <int>     tris    = new List <int>();

                SimpleTransform firstStep = new SimpleTransform(), lastStep = track.Curve.GetPointAt(1);

                var caps     = (track.PrevTrack ? 0 : 1) + (track.NextTrack ? 0 : 1);
                //We assume that the start and end caps have the same number of verts/tris, if not this will run a little slower or use more memory.
                var vertCount = caps * capMesh.verts.Count / 2;
                var triCount = caps * capMesh.tris.Count / 2;
                allVerts.EnsureSpace(vertCount);
                allNormals.EnsureSpace(vertCount);
                allUVs.EnsureSpace(vertCount);
                allTris.EnsureSpace(triCount);

                for (int triIdx = 0; triIdx < capMesh.tris.Count; triIdx += 3)
                {
                    //grab the first vert in the face to see what side the face is on
                    Vector3 pos0 = capMesh.verts[capMesh.tris[triIdx + 0]].pos;

                    float           offset;  //how much to offset Z to zero out the tri
                    SimpleTransform trans;

                    if (pos0.z < 0)
                    {
                        //this tri falls on the start side
                        if (track.PrevTrack && !track.forceEndCaps)
                        {
                            continue;                                                         //can't see this cap
                        }
                        offset = .5f;
                        trans  = firstStep;
                    }
                    else
                    {
                        //this tri falls on the end side
                        if (track.NextTrack && !track.forceEndCaps)
                        {
                            continue;                                                         //can't see this cap
                        }
                        offset = -.5f;
                        trans  = lastStep;
                    }

                    //Copy each vert from this face
                    for (int i = 0; i < 3; ++i)
                    {
                        int idx = capMesh.tris[triIdx + i];

                        Vertex  v    = capMesh.verts[idx];
                        Vector3 vPos = v.pos;
                        vPos.z += offset;

                        tris.Add(verts.Count);
                        verts.Add(trans * vPos);
                        normals.Add(trans.rotation * v.normal);
                        uvs.Add(v.uv);
                    }
                }

                //Add intermediate results to the final mesh
                int vertOffset = allVerts.Count;
                allVerts.AddRange(verts);
                allNormals.AddRange(normals);
                allUVs.AddRange(uvs);
                foreach (var vertIdx in tris)
                {
                    allTris.Add(vertIdx + vertOffset);
                }
            }

            if (tieMesh)
            {
                //Tie mesh should consist of some polygons we can throw in at intervals to tie the track together.

                var trackLength = track.Length;

                var numTies = Mathf.Floor(trackLength / track.tieInterval);
                // Give the number of ties a reasonable limit.
                if (track.tieInterval <= 0)
                {
                    numTies = 0;
                }
                if (numTies > MaxTiesCount)
                {
                    numTies = MaxTiesCount;
                }

                // Space ties evenly along the length (results in a slightly higher tie interval than requested)
                var effectiveInterval = trackLength / numTies;

                var offset = .5f * effectiveInterval;

                var tieVerts   = tieMesh.vertices;
                var tieNormals = tieMesh.normals;
                var uvTris     = tieMesh.uv;
                var tieTris    = tieMesh.triangles;

                var vertCount = (int)numTies * tieVerts.Length;
                var triCount  = (int)numTies * tieTris.Length;
                allVerts.EnsureSpace(vertCount);
                allNormals.EnsureSpace(vertCount);
                allUVs.EnsureSpace(vertCount);
                allTris.EnsureSpace(triCount);

                var intervalIter = track.Curve.GetIntervals(offset, effectiveInterval).GetEnumerator();
                for (int i = 0; i < numTies; i++)
                {
                    if (!intervalIter.MoveNext())
                    {
                        Debug.LogWarning("Tie count didn't match up", track);
                        break;
                    }

                    var tiePos   = intervalIter.Current;
                    int pInitial = allVerts.Count;

                    //allVerts.AddRange(from vPos in tieMesh.vertices select tiePos * vPos);
                    for (int j = 0; j < tieVerts.Length; ++j)
                    {
                        allVerts.Add(tiePos * tieVerts[j]);
                    }

                    //allNormals.AddRange(from normal in tieMesh.normals select tiePos.rotation * normal);
                    for (int j = 0; j < tieNormals.Length; ++j)
                    {
                        allNormals.Add(tiePos.rotation * tieNormals[j]);
                    }

                    //allUVs.AddRange(tieMesh.uv);
                    for (int j = 0; j < uvTris.Length; ++j)
                    {
                        allUVs.Add(uvTris[j]);
                    }

                    //allTris.AddRange(from idx in tieMesh.triangles select idx + pInitial);
                    for (int j = 0; j < tieTris.Length; ++j)
                    {
                        allTris.Add(pInitial + tieTris[j]);
                    }
                }
            }

                #if UNITY_EDITOR
            if (!Application.isPlaying)
            {
                //If the mesh hasn't changed, don't modify it while editing. This will avoid marking the scene as changed when it hasn't.
                //During play mode, don't worry about it. It's stupid to burn CPU to avoid a no-op at this point.

                if (
                    allVerts.SequenceEqual(mesh.vertices) &&
                    allNormals.SequenceEqual(mesh.normals) &&
                    allUVs.SequenceEqual(mesh.uv) &&
                    allTris.SequenceEqual(mesh.triangles)
                    )
                {
                    //turns out, we didn't need to do anything after all
                    return;
                }
            }
                #endif

            mesh.Clear();
            mesh.vertices  = allVerts.ToArray();
            mesh.normals   = allNormals.ToArray();
            mesh.uv        = allUVs.ToArray();
            mesh.triangles = allTris.ToArray();
        }