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; } }
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; }
/** * 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); }
/** * 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); }
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; } }
/** * 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; } } }
/** * 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)); }
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 ); } } }
/** * 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()); }
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; } }
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; } }
/** * 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(); }