//helper function to determine all viable snap candidates given an actor and a starting position // will automatically reject candidates outside the configured min snap offset // will also automatically weight the previous frame's results higher to reduce jitter between two close snap points private void AddToCandidates(GameThing thing, Vector3 snapFromPosition, int sourceIndex, ref List <CandidateConnection> snapCandidates) { //loop over all game things looking for pipes for (int i = 0; i < InGame.inGame.gameThingList.Count; i++) { GameActor actor = InGame.inGame.gameThingList[i] as GameActor; if (actor != null && actor.Movement != null && actor != (thing as GameActor)) { if (actor.Chassis != null && actor.Chassis is PipeChassis) { //found a pipe that isn't ourself, look for candidates PipeChassis targetPipe = actor.Chassis as PipeChassis; List <PipeConnection> potentialSnapPoints = GenerateConnections(actor, actor.Movement.Position); //weight each candidate based on how far away it is - if too far, reject it completely foreach (PipeConnection nextSnapPoint in potentialSnapPoints) { Vector3 snapVector = nextSnapPoint.SourcePos - snapFromPosition; snapVector.Z = 0.0f; float distance = snapVector.Length(); //if distance is in max offset or, if it's exactly where we snapped to last time, allow for double the radius to consider it valid if (distance <= kMaxSnapOffset * Parent.ReScale || (m_bIsSnapped && m_lastSnapSourceIndex == sourceIndex && distance < kMaxSnapOffset * Parent.ReScale * 2.0f)) { float weight = 1.0f - MathHelper.Clamp(distance / kMaxSnapOffset, 0.0f, 1.0f); bool retainSnap = false; //special case: were we previously snapped to this exact target, from the same source? // if so, add some weight - we'd prefer to stay in place if (m_bIsSnapped && m_lastSnapSourceIndex == sourceIndex) { float distanceToLastSnapped = (m_lastSnapTarget - nextSnapPoint.SourcePos).Length(); if (distanceToLastSnapped < 0.001f) { weight += 1.0f; retainSnap = true; } } //found a candidate, add it to the lsit CandidateConnection newCandidate = new CandidateConnection { SourceConnectionIndex = sourceIndex, SnapToPosition = nextSnapPoint.SourcePos, UpDir = nextSnapPoint.SourceUpDir, CenterPosition = actor.Movement.Position, Weight = weight, RetainSnap = retainSnap }; snapCandidates.Add(newCandidate); } } } } } }
//helper function that, based on a given actor and reference position, will find the best connection to snap to //returns false if no connection found (pipe is not close to other pipes) private bool FindSnapToConnection(GameThing thing, Vector3 snapFromPosition, out PipeConnection snapToConnection) { snapToConnection = new PipeConnection { SourcePos = snapFromPosition, TargetPos = snapFromPosition }; GameActor actor = thing as GameActor; if (actor == null || actor.Movement == null) { return(false); } //build a list of connections based on our pipe type List <PipeConnection> connections = GenerateConnections(actor, snapFromPosition); //generate a list of all candidate snap to targets List <CandidateConnection> candidates = new List <CandidateConnection>(); for (int i = 0; i < connections.Count; ++i) { PipeConnection connection = connections[i]; AddToCandidates(thing, connection.SourcePos, i, ref candidates); } //no snap to targets? early out... if (candidates.Count <= 0) { m_bIsSnapped = false; return(false); } //find the candidate with the highest weight CandidateConnection bestCandidate = new CandidateConnection(); bestCandidate.Weight = float.MinValue; for (int j = 0; j < candidates.Count; ++j) { if (candidates[j].Weight > bestCandidate.Weight) { bestCandidate = candidates[j]; } } //update the snap connection we're building based on the best candidate's values snapToConnection = connections[bestCandidate.SourceConnectionIndex]; //if we were already snapped and our candidate is the same, then don't recalculate push-together amount //based on up dir difference, bump the snap position closer to the center of the target object float cosAngle = Vector3.Dot(bestCandidate.UpDir, connections[bestCandidate.SourceConnectionIndex].SourceUpDir); Vector3 pushTogetherDirection = bestCandidate.CenterPosition - bestCandidate.SnapToPosition; pushTogetherDirection.Normalize(); /////////////////////////////////////////// //JITTER REDUCTION + PUSH TOGETHER LOGIC // The below code pushes together pipe pieces whose up vectors are not aligned, attempting to cover gaps // It also looks for cases where the source conditions were identical to last frame, and uses cached values // instead if so, preventing jitter /////////////////////////////////////////// //if snapping to same place, only allow push together amount if it is larger Vector3 pushTogetherAmount = pushTogetherDirection * kPushTogetherFactor * (1.0f - cosAngle); if (m_bIsSnapped && bestCandidate.RetainSnap) { //if the cursor hasn't moved and we're still snapped, don't ever update the source pos float deltaSnapFrom = (m_lastSnapFromPos - snapFromPosition).Length(); if (deltaSnapFrom < 0.01f) { //same connection and cursor hasn't moved, override the source pos to match what we had last time snapToConnection.SourcePos = m_lastSourcePos; } else { //cursor was moved, update source info m_lastSnapFromPos = snapFromPosition; m_lastSourcePos = snapToConnection.SourcePos; } //check if push together amount has a better value than last time if (pushTogetherAmount.LengthSquared() > m_lastPushTogetherAmount.LengthSquared()) { //this push amount is better, let's use it snapToConnection.TargetPos = bestCandidate.SnapToPosition + pushTogetherAmount; m_lastPushTogetherAmount = pushTogetherAmount; } else { //our previously value still the best, keep it snapToConnection.TargetPos = bestCandidate.SnapToPosition + m_lastPushTogetherAmount; } } else { //we weren't snapped here before, update various state snapToConnection.TargetPos = bestCandidate.SnapToPosition + pushTogetherAmount; m_lastPushTogetherAmount = pushTogetherAmount; m_lastSnapFromPos = snapFromPosition; m_lastSourcePos = snapToConnection.SourcePos; } //if we get this far, we found a snap taget, update some final values and return true m_bIsSnapped = true; m_lastSnapSourceIndex = bestCandidate.SourceConnectionIndex; m_lastSnapTarget = bestCandidate.SnapToPosition; //where were we trying to snap to? return(true); }