示例#1
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            var heightMap = HeightMapManager.instance.GetHeightMap(unit.position);

            var lookAhead    = heightMap.granularity;
            var unitRadius   = unit.radius;
            var offsetFactor = (unitRadius + lookAhead);
            var perpOffset   = new Vector3(-velocityNormal.z * offsetFactor, 0f, velocityNormal.x * offsetFactor);

            var center     = unit.position;
            var backLeft   = center - (velocityNormal * unitRadius) - perpOffset;
            var backRight  = center - (velocityNormal * unitRadius) + perpOffset;
            var frontLeft  = center + (velocityNormal * (unitRadius + lookAhead)) - perpOffset;
            var frontRight = center + (velocityNormal * (unitRadius + lookAhead)) + perpOffset;

            _heightCandidates[0] = heightMap.SampleHeight(backLeft);
            _heightCandidates[1] = heightMap.SampleHeight(backRight);
            _heightCandidates[2] = heightMap.SampleHeight(frontLeft);
            _heightCandidates[3] = heightMap.SampleHeight(frontRight);
            _heightCandidates[4] = heightMap.SampleHeight(center);

            var maxHeight = Mathf.Max(_heightCandidates);

            //Ground pos cannot be less than actual ground level. The reason it can be higher is if the unit is in free fall, in which case gravity will handle the rest.
            var groundPos = Mathf.Max(center.y - unit.baseToPositionOffset, _heightCandidates[4]);

            var diff = maxHeight - groundPos;

            return(diff <= unit.heightNavigationCapability.maxClimbHeight ? maxHeight + unit.baseToPositionOffset : _heightCandidates[4] + unit.baseToPositionOffset);
        }
示例#2
0
        /// <summary>
        /// Gets the desired steering output.
        /// </summary>
        /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param>
        /// <param name="output">The steering output to be populated.</param>
        public override void GetDesiredSteering(SteeringInput input, SteeringOutput output)
        {
            _selfCollisionPos = Vector3.zero;
            _lastAvoidVector  = Vector3.zero;
            _lastAvoidPos     = Vector3.zero;

            var otherUnits  = _scanner.units;
            int othersCount = otherUnits.count;

            if (othersCount == 0)
            {
                // if the scanner has found no units to avoid, exit
                return;
            }

            _unitData = input.unit;

            Vector3 avoidVector = Avoid(otherUnits, othersCount, input.currentPlanarVelocity);

            if (avoidVector.sqrMagnitude < (this.minimumAvoidVectorMagnitude * this.minimumAvoidVectorMagnitude))
            {
                // if the computed avoid vector's magnitude is less than the minimumAvoidVectorMagnitude, then discard it
                return;
            }

            // apply the avoidance force as a full deceleration capped force (not over time)
            Vector3 steeringVector = Vector3.ClampMagnitude(avoidVector / Time.fixedDeltaTime, input.maxDeceleration);

            _lastAvoidVector           = steeringVector;
            output.desiredAcceleration = steeringVector;
        }
示例#3
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            var radius = unit.radius + _lookAhead;
            var start = unit.basePosition;
            var baseHeight = start.y;
            var maxClimb = unit.heightNavigationCapability.maxClimbHeight;

            //Put the start at the unit's head or max climb above its center
            start.y = matrix != null ? matrix.origin.y + matrix.upperBoundary : start.y + Mathf.Max(unit.height, radius + maxClimb);

            RaycastHit hit;
            if (!Physics.SphereCast(start, radius, Vector3.down, out hit, Mathf.Infinity, Layers.terrain))
            {
                return Consts.InfiniteDrop;
            }

            var sampledHeight = hit.point.y;

            var diff = sampledHeight - baseHeight;

            if (diff <= maxClimb)
            {
                return sampledHeight + unit.baseToPositionOffset;
            }

            return SampleHeight(unit.position, matrix) + unit.baseToPositionOffset;
        }
示例#4
0
 public ApiController(IUnitGroupFacade unitGroupFacade, IUnitFacade unitFacade, IColorFacade colorFacade, IUnitTypeFacade unitTypeFacade)
 {
     _unitGroupFacade = unitGroupFacade;
     _unitFacade      = unitFacade;
     _colorFacade     = colorFacade;
     _unitTypeFacade  = unitTypeFacade;
 }
示例#5
0
        private void StartPendle(IUnitFacade steerer)
        {
            steerer.DisableMovementOrders();
            steerer.Wait(null);

            var curParent = steerer.transform.parent;
            var rb        = steerer.gameObject.GetComponent <Rigidbody>();

            if (this.isOnboardTrigger)
            {
                rb.isKinematic           = true;
                steerer.transform.parent = pendler.transform;
            }

            pendler.MoveTo(
                this.movesTo,
                () =>
            {
                if (this.isOnboardTrigger)
                {
                    steerer.transform.parent = curParent;
                    rb.isKinematic           = false;
                }
                else
                {
                    steerer.EnableMovementOrders();
                }

                steerer.Resume();
            });
        }
        /// <summary>
        /// Convenience method for telling this unit to start solo pathing along a given path.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="unit">The unit.</param>
        private void FollowPath(Path path, IUnitFacade unit)
        {
            unit.MoveAlong(path);
            _requestedPath = false;

            StartSoloPath();
        }
示例#7
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            var radius     = unit.radius + _lookAhead;
            var start      = unit.basePosition;
            var baseHeight = start.y;
            var maxClimb   = unit.heightNavigationCapability.maxClimbHeight;

            //Put the start at the unit's head or max climb above its center
            start.y = matrix != null ? matrix.origin.y + matrix.upperBoundary : start.y + Mathf.Max(unit.height, radius + maxClimb);

            RaycastHit hit;

            if (!Physics.SphereCast(start, radius, Vector3.down, out hit, Mathf.Infinity, Layers.terrain))
            {
                return(Consts.InfiniteDrop);
            }

            var sampledHeight = hit.point.y;

            var diff = sampledHeight - baseHeight;

            if (diff <= maxClimb)
            {
                return(sampledHeight + unit.baseToPositionOffset);
            }

            return(SampleHeight(unit.position, matrix) + unit.baseToPositionOffset);
        }
示例#8
0
        /// <summary>
        /// Toggles the selected state of a unit. Actually it only toggles when <paramref name="append"/> is true, otherwise it will select the unit while deselecting all others.
        /// </summary>
        /// <param name="unit">The unit.</param>
        /// <param name="append">if set to <c>true</c> [append].</param>
        public void ToggleSelected(IUnitFacade unit, bool append)
        {
            //So this works differently depending on the append modifier. If append then the unit is toggled, if not the unit is not toggled but always selected while all others aren't.
            if (!append || _selected == _emptyGroup)
            {
                DeselectAll();

                unit.isSelected = true;
                _selected       = GroupingManager.CreateGrouping(unit);
                return;
            }

            unit.isSelected = !unit.isSelected;

            if (unit.isSelected)
            {
                _selected.Add(unit);
            }
            else
            {
                _selected.Remove(unit);
            }

            PostUnitsSelectedMessage(_selected);
        }
示例#9
0
        /// <summary>
        /// Unregisters the a unit as selectable.
        /// </summary>
        /// <param name="unit">The unit.</param>
        public void UnregisterSelectable(IUnitFacade unit)
        {
            //The unit will be removed from its transient group so we do not need to do that here
            unit.isSelected = false;
            _selectableUnits.Remove(unit);

            //No need to create an enumerator if the groups dict is empty
            if (_groups.Count > 0)
            {
                foreach (var grpEntry in _groups)
                {
                    grpEntry.Value.Remove(unit);
                    if (grpEntry.Value.memberCount == 0)
                    {
                        _removalBuffer.Add(grpEntry.Key);
                    }
                }

                for (int i = 0; i < _removalBuffer.Count; i++)
                {
                    _groups.Remove(_removalBuffer[i]);
                }

                _removalBuffer.Clear();
            }
        }
示例#10
0
        /// <summary>
        /// Stops the unit's patrol.
        /// </summary>
        /// <param name="unit">The unit.</param>
        public static void StopPatrol(this IUnitFacade unit)
        {
            PatrolClient client;

            if (!_clients.TryGetValue(unit.gameObject, out client))
            {
                if (!_pausedClients.TryGetValue(unit.gameObject, out client))
                {
                    return;
                }
            }

            _clients.Remove(unit.gameObject);
            _pausedClients.Remove(unit.gameObject);

            if (_clients.Count == 0)
            {
                _handler.Stop();
                _handler = null;
            }

            if (unit.isAlive)
            {
                client.Stop();
            }
        }
示例#11
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            var heightMap = HeightMapManager.instance.GetHeightMap(unit.position);

            var lookAhead = heightMap.granularity;
            var unitRadius = unit.radius;
            var offsetFactor = (unitRadius + lookAhead);
            var perpOffset = new Vector3(-velocityNormal.z * offsetFactor, 0f, velocityNormal.x * offsetFactor);

            var center = unit.position;
            var backLeft = center - (velocityNormal * unitRadius) - perpOffset;
            var backRight = center - (velocityNormal * unitRadius) + perpOffset;
            var frontLeft = center + (velocityNormal * (unitRadius + lookAhead)) - perpOffset;
            var frontRight = center + (velocityNormal * (unitRadius + lookAhead)) + perpOffset;

            _heightCandidates[0] = heightMap.SampleHeight(backLeft);
            _heightCandidates[1] = heightMap.SampleHeight(backRight);
            _heightCandidates[2] = heightMap.SampleHeight(frontLeft);
            _heightCandidates[3] = heightMap.SampleHeight(frontRight);
            _heightCandidates[4] = heightMap.SampleHeight(center);

            var maxHeight = Mathf.Max(_heightCandidates);

            //Ground pos cannot be less than actual ground level. The reason it can be higher is if the unit is in free fall, in which case gravity will handle the rest.
            var groundPos = Mathf.Max(center.y - unit.baseToPositionOffset, _heightCandidates[4]);

            var diff = maxHeight - groundPos;
            return diff <= unit.heightNavigationCapability.maxClimbHeight ? maxHeight + unit.baseToPositionOffset : _heightCandidates[4] + unit.baseToPositionOffset;
        }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float forward = Mathf.Abs((count * 0.5f) - formationIndex) * -0.5f * _formationSpacing;
            float right   = ((count * 0.5f) - formationIndex) * -_formationSpacing;

            return(new Vector3(right, 0f, forward));
        }
示例#13
0
        /// <summary>
        /// Called on Awake
        /// </summary>
        protected override void Awake()
        {
            base.Awake();

            this.WarnIfMultipleInstances();

            _transform = this.transform;

            _wayPoints          = new SimpleQueue <Vector3>();
            _pathboundWayPoints = new SimpleQueue <Vector3>();
            _navMessage         = new UnitNavigationEventMessage(this.gameObject);

            _resultProcessors = this.GetComponents <SteerForPathResultProcessorComponent>();
            Array.Sort(_resultProcessors, (a, b) => a.processingOrder.CompareTo(b.processingOrder));

            _unit         = this.GetUnitFacade();
            _pathSettings = _unit.pathNavigationOptions;

            if (this.arrivalDistance > _pathSettings.nextNodeDistance)
            {
                Debug.LogError("The Arrival Distance must be equal to or less that the Next Node Distance.");
            }

            _stopped = true;
            _unit.hasArrivedAtDestination = true;
        }
示例#14
0
        private void Awake()
        {
            _radius = this.GetComponent <SphereCollider>().radius;

            //This could be more elegantly made, but will suffice for this example
            _workerModel = FindObjectOfType <PickupWorker>().GetUnitFacade();
        }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float forward = Mathf.Abs((count * 0.5f) - formationIndex) * -0.5f * _formationSpacing;
            float right = ((count * 0.5f) - formationIndex) * -_formationSpacing;

            return new Vector3(right, 0f, forward);
        }
示例#16
0
        private void SpawnUnit()
        {
            var go = Instantiate(this.unitMold, this.spawnPosition, Quaternion.identity) as GameObject;

            go.SetActive(true);

            _unit = go.GetUnitFacade();
        }
示例#17
0
        private void Start()
        {
            _unit = this.GetUnitFacade();

            if (_master != null)
            {
                _master.RegisterWorker(this);
            }
        }
示例#18
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            if (matrix != null)
            {
                return matrix.origin.y + unit.baseToPositionOffset;
            }

            return unit.position.y;
        }
 protected override string OnInit()
 {
     unit = (agent as Component).GetUnitFacade();
     if (unit == null)
     {
         return("Unable to create Unit Facade");
     }
     return(null);
 }
示例#20
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            if (matrix != null)
            {
                return(matrix.origin.y + unit.baseToPositionOffset);
            }

            return(unit.position.y);
        }
示例#21
0
 /// <summary>
 /// Creates a path request for the unit.
 /// </summary>
 /// <param name="unit">The unit.</param>
 /// <param name="to">The destination.</param>
 /// <param name="callback">TThe callback to be called when the result is ready.</param>
 /// <returns>A path request for the unit.</returns>
 public static IPathRequest CreatePathRequest(this IUnitFacade unit, Vector3 to, Action <PathResult> callback)
 {
     return(new CallbackPathRequest(callback)
     {
         from = unit.position,
         to = to,
         requesterProperties = unit,
         pathFinderOptions = unit.pathFinderOptions
     });
 }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            int columns = Mathf.CeilToInt(Mathf.Sqrt(count));
            int rows    = Mathf.FloorToInt(count / (float)columns);

            float column = (formationIndex % columns) - (columns * 0.5f);
            float row    = Mathf.FloorToInt((formationIndex / columns) - (rows * 0.5f));

            return(new Vector3((column * _formationSpacing), 0f, (-row * _formationSpacing)));
        }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            int columns = Mathf.CeilToInt(Mathf.Sqrt(count));
            int rows = Mathf.FloorToInt(count / (float)columns);

            float column = (formationIndex % columns) - (columns * 0.5f);
            float row = Mathf.FloorToInt((formationIndex / columns) - (rows * 0.5f));

            return new Vector3((column * _formationSpacing), 0f, (-row * _formationSpacing));
        }
示例#24
0
    void Awake()
    {
        _unitFacade = this.GetUnitFacade();
        _apexUnit   = this.As <IMovable> ();

        if (_dynamicObstacle != null)
        {
            setDynamicAINumber();
        }
    }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float radius = count / (2f * Mathf.PI) * _formationRadius;
            float step = (2f * Mathf.PI) / count;

            float t = step * formationIndex;
            float x = Mathf.Cos(t);
            float z = Mathf.Sin(t);

            return new Vector3(x, 0f, z) * radius;
        }
示例#26
0
        /// <summary>
        /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
        /// </summary>
        protected override void OnStartAndEnable()
        {
            _heightSampler = GameServices.heightStrategy.heightSampler;

            if (_unit == null)
            {
                _unit = this.GetUnitFacade();
            }

            base.OnStartAndEnable();
        }
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float radius = count / (2f * Mathf.PI) * _formationRadius;
            float step   = (2f * Mathf.PI) / count;

            float t = step * formationIndex;
            float x = Mathf.Cos(t);
            float z = Mathf.Sin(t);

            return(new Vector3(x, 0f, z) * radius);
        }
示例#28
0
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float radius = count / (2f * Mathf.PI) * _formationRadius;
            float i = (float)formationIndex / count;
            float angle = i * Mathf.PI * 2f;

            float x = Mathf.Cos(angle);
            float z = Mathf.Sin(angle);

            return new Vector3(x, 0f, z) * radius * (0.5f + i);
        }
        /// <summary>
        /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
        /// </summary>
        protected override void OnStartAndEnable()
        {
            _heightSampler = GameServices.heightStrategy.heightSampler;

            if (_unit == null)
            {
                _unit = this.GetUnitFacade();
            }

            base.OnStartAndEnable();
        }
示例#30
0
        /// <summary>
        /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
        /// </summary>
        /// <param name="count">The group's member count.</param>
        /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
        /// <param name="unit">The unit to calculate this particular formation position for.</param>
        /// <returns>
        /// A single formation position based on the count, index and the particular unit.
        /// </returns>
        public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
        {
            float radius = count / (2f * Mathf.PI) * _formationRadius;
            float i      = (float)formationIndex / count;
            float angle  = i * Mathf.PI * 2f;

            float x = Mathf.Cos(angle);
            float z = Mathf.Sin(angle);

            return(new Vector3(x, 0f, z) * radius * (0.5f + i));
        }
示例#31
0
        private void Awake()
        {
            this.WarnIfMultipleInstances();

            _unit = this.GetUnitFacade();
            if (_unit == null)
            {
                Debug.LogError("WanderBehaviour requires a component that implements IMovable.");
                this.enabled = false;
            }
        }
示例#32
0
        /// <summary>
        /// Stops the unit from wandering.
        /// </summary>
        /// <param name="unit">The unit.</param>
        public static void StopWander(this IUnitFacade unit)
        {
            _clients.Remove(unit.gameObject);

            if (_clients.Count == 0)
            {
                _handler.Stop();
                _handler = null;
            }

            unit.Stop();
        }
示例#33
0
        /// <summary>
        /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
        /// </summary>
        protected override void OnStartAndEnable()
        {
            NavLoadBalancer.scanners.Add(this, this.scanInterval, false);

            _unitData = this.GetUnitFacade();
            _units    = new DynamicArray <IUnitFacade>(5);

            if (this.forecastDistance > this.scanRadius)
            {
                // forecast distance can never surpass the scan radius
                this.forecastDistance = this.scanRadius;
            }
        }
示例#34
0
        /// <summary>
        /// Removes the specified member from this steering group.
        /// Also disposes itself if the last member is removed.
        /// </summary>
        public override void Remove(IUnitFacade member)
        {
            base.Remove(member);

            if (this.count == 0)
            {
                // if the last member was just removed, the group should dispose of and cleanup itself
                CleanupGroup();
            }
            else if (_currentFormation != null && _modelUnit.isAlive)
            {
                SetFormation(_currentFormation);
            }
        }
        /// <summary>
        /// Called on Start
        /// </summary>
        protected override void Start()
        {
            base.Start();

            //Get unit ref
            _unit = this.GetUnitFacade();
            _steeringInput.unit = _unit;

            //Init other vars
            _lastFramePosition = _unit.position;
            _stopped           = true;

            UpdateInput();
        }
示例#36
0
        /// <summary>
        /// Adds the specified member to this steering group.
        /// Also initializes model unit-based settings, if they haven't already been initialized.
        /// </summary>
        public override void Add(IUnitFacade member)
        {
            base.Add(member);

            // if the group was created using the 'capacity' ctor overload, we must create model unit when the first unit is added
            if (_modelUnit == null)
            {
                CreateModelUnit();
                InitializeModelUnitSettings();

                _groupCog    = member.position;
                _currentGrid = GridManager.instance.GetGrid(_groupCog);
            }
        }
示例#37
0
        /// <summary>
        /// Pauses the patrol.
        /// </summary>
        /// <param name="unit">The unit.</param>
        public static void PausePatrol(this IUnitFacade unit)
        {
            PatrolClient client;

            if (!_clients.TryGetValue(unit.gameObject, out client))
            {
                return;
            }

            _clients.Remove(unit.gameObject);
            _pausedClients[unit.gameObject] = client;

            client.Stop();
        }
        /// <summary>
        /// Convenience method for queuing up path requests.
        /// </summary>
        /// <param name="fromPos">From position.</param>
        /// <param name="toPos">To position.</param>
        /// <param name="unitData">The unit UnitFacade.</param>
        /// <param name="callback">The path request callback.</param>
        private void QueuePathRequest(Vector3 fromPos, Vector3 toPos, IUnitFacade unitData, Action <PathResult> callback)
        {
            _requestedPath = true;

            // request a path...
            var req = new CallbackPathRequest(callback)
            {
                from = fromPos,
                to   = toPos,
                requesterProperties = unitData,
                pathFinderOptions   = unitData.pathFinderOptions,
            };

            GameServices.pathService.QueueRequest(req, unitData.pathFinderOptions.pathingPriority);
        }
示例#39
0
        public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal)
        {
            var unitRadius = unit.radius;
            var perpOffset = new Vector3(-velocityNormal.z * unitRadius, 0f, velocityNormal.x * unitRadius);

            var center = unit.position;
            _samplePoints[0] = center - (velocityNormal * unitRadius) - perpOffset;
            _samplePoints[1] = center - (velocityNormal * unitRadius) + perpOffset;
            _samplePoints[2] = center + (velocityNormal * (unitRadius + _lookAhead)) - perpOffset;
            _samplePoints[3] = center + (velocityNormal * (unitRadius + _lookAhead)) + perpOffset;
            _samplePoints[4] = center;

            float maxClimb = unit.heightNavigationCapability.maxClimbHeight;

            float rayStart = matrix != null ? matrix.origin.y + matrix.upperBoundary : center.y + maxClimb;
            float sampledHeight = Consts.InfiniteDrop;
            float maxHeight = Consts.InfiniteDrop;

            RaycastHit hit;
            for (int i = 0; i < 5; i++)
            {
                _samplePoints[i].y = rayStart;

                if (Physics.Raycast(_samplePoints[i], Vector3.down, out hit, Mathf.Infinity, Layers.terrain))
                {
                    sampledHeight = hit.point.y;
                    if (sampledHeight > maxHeight)
                    {
                        maxHeight = sampledHeight;
                    }
                }
            }

            var diff = maxHeight - unit.basePosition.y;

            //Since sampledHeight will be the height at the center we can just use that directly here in case the max height is too high.
            return diff <= maxClimb ? maxHeight + unit.baseToPositionOffset : sampledHeight + unit.baseToPositionOffset;
        }
        /// <summary>
        /// Called on Start
        /// </summary>
        protected override void Start()
        {
            base.Start();

            _scanner = this.GetComponent<SteeringScanner>();
            if (_scanner == null)
            {
                Debug.LogError(this.gameObject.name + " is missing its SteeringScanner component");
            }

            _unitData = this.GetUnitFacade();

            float deg2Rad = Mathf.Deg2Rad;
            _fovReverseAngleCos = Mathf.Cos(((360f - _unitData.fieldOfView) / 2f) * deg2Rad);
            _omniAwareRadius = _unitData.radius * 2f;
            _cosAvoidAngle = Mathf.Cos(this.headOnCollisionAngle * deg2Rad);
        }
        /// <summary>
        /// Convenience method for telling this unit to start solo pathing along a given path.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="unit">The unit.</param>
        private void FollowPath(Path path, IUnitFacade unit)
        {
            unit.MoveAlong(path);
            _requestedPath = false;

            StartSoloPath();
        }
        /// <summary>
        /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
        /// </summary>
        protected override void OnStartAndEnable()
        {
            NavLoadBalancer.scanners.Add(this, this.scanInterval, false);

            _unitData = this.GetUnitFacade();
            _units = new DynamicArray<IUnitFacade>(5);

            if (this.forecastDistance > this.scanRadius)
            {
                // forecast distance can never surpass the scan radius
                this.forecastDistance = this.scanRadius;
            }
        }
        /// <summary>
        /// Called on Awake
        /// </summary>
        protected override void Awake()
        {
            base.Awake();

            this.WarnIfMultipleInstances();

            _transform = this.transform;

            _wayPoints = new WaypointList();
            _navMessage = new UnitNavigationEventMessage(this.gameObject);

            _resultProcessors = this.GetComponents<SteerForPathResultProcessorComponent>();
            Array.Sort(_resultProcessors, (a, b) => a.processingOrder.CompareTo(b.processingOrder));

            _unit = this.GetUnitFacade();
            _pathSettings = _unit.pathNavigationOptions;

            _stopped = true;
        }
 /// <summary>
 /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
 /// </summary>
 /// <param name="count">The group's member count.</param>
 /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
 /// <param name="unit">The unit to calculate this particular formation position for.</param>
 /// <returns>
 /// A single formation position based on the count, index and the particular unit.
 /// </returns>
 public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
 {
     float half = (count * 0.5f);
     return new Vector3(0f, 0f, (formationIndex - half) * _formationSpacing);
 }
示例#45
0
 /// <summary>
 /// Called on start
 /// </summary>
 protected override void Start()
 {
     _unit = this.GetUnitFacade();
 }
        /// <summary>
        /// Gets the desired steering output.
        /// </summary>
        /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param>
        /// <param name="output">The steering output to be populated.</param>
        public override void GetDesiredSteering(SteeringInput input, SteeringOutput output)
        {
            _selfCollisionPos = Vector3.zero;
            _lastAvoidVector = Vector3.zero;
            _lastAvoidPos = Vector3.zero;

            var otherUnits = _scanner.units;
            int othersCount = otherUnits.count;
            if (othersCount == 0)
            {
                // if the scanner has found no units to avoid, exit
                return;
            }

            _unitData = input.unit;

            Vector3 avoidVector = Avoid(otherUnits, othersCount, input.currentPlanarVelocity);
            if (avoidVector.sqrMagnitude < (this.minimumAvoidVectorMagnitude * this.minimumAvoidVectorMagnitude))
            {
                // if the computed avoid vector's magnitude is less than the minimumAvoidVectorMagnitude, then discard it
                return;
            }

            // apply the avoidance force as a full deceleration capped force (not over time)
            Vector3 steeringVector = Vector3.ClampMagnitude(avoidVector / Time.fixedDeltaTime, input.maxDeceleration);

            _lastAvoidVector = steeringVector;
            output.desiredAcceleration = steeringVector;
        }
        /// <summary>
        /// Gets the desired steering output.
        /// </summary>
        /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param>
        /// <param name="output">The steering output to be populated.</param>
        public override void GetDesiredSteering(SteeringInput input, SteeringOutput output)
        {
            // Set the formation position to null to make sure that it is invalid when it's supposed to be
            input.unit.formationPos = null;
            _formationPos = null;

            // set instance variables to be used in load balanced update method
            _unitData = input.unit;
            _grid = input.grid;

            if (_grid == null)
            {
                // if not on a grid, drop formation
                return;
            }

            // must cast the group to have access to GetFormationPosition
            var group = _unitData.transientGroup as DefaultSteeringTransientUnitGroup;
            if (group == null)
            {
                // if not in a group, drop formation
                return;
            }

            int count = group.count;
            if (count == 0)
            {
                // if there are no members in the group, drop formation
                return;
            }

            var modelUnit = group.modelUnit;
            if (modelUnit == null)
            {
                // if group's model unit is missing, then drop formation
                return;
            }

            if (_unitData.formationIndex < 0)
            {
                // if this unit has not been given a valid formation index, then drop formation
                return;
            }

            if (_unitData.hasArrivedAtDestination && this.dropFormationOnArrival)
            {
                return;
            }

            _formationPos = group.GetFormationPosition(_unitData.formationIndex);
            if (_formationPos == null || _stopped)
            {
                // if there is no valid formation position calculated or it is invalid currently, then drop formation
                return;
            }

            // make sure desiredSpeed does not spill over or under
            var desiredSpeed = Mathf.Min(1.5f * input.desiredSpeed, _unitData.maximumSpeed);
            
            _unitData.formationPos = _formationPos;
            Vector3 arrivalVector = Arrive(_formationPos.position, input, desiredSpeed);
            if (modelUnit.hasArrivedAtDestination && this.hasArrived)
            {
                // When the unit arrives at the designated formation position, make sure hasArrivedAtDestination becomes true
                // Also, must return here, to avoid outputting a result
                _unitData.hasArrivedAtDestination = true;
                return;
            }

            output.maxAllowedSpeed = desiredSpeed;
            output.desiredAcceleration = arrivalVector;
        }
        /// <summary>
        /// Convenience method for queuing up path requests.
        /// </summary>
        /// <param name="fromPos">From position.</param>
        /// <param name="toPos">To position.</param>
        /// <param name="unitData">The unit UnitFacade.</param>
        /// <param name="callback">The path request callback.</param>
        private void QueuePathRequest(Vector3 fromPos, Vector3 toPos, IUnitFacade unitData, Action<PathResult> callback)
        {
            _requestedPath = true;

            // request a path...
            var req = new CallbackPathRequest(callback)
            {
                from = fromPos,
                to = toPos,
                requesterProperties = unitData,
                pathFinderOptions = unitData.pathFinderOptions,
            };

            GameServices.pathService.QueueRequest(req, unitData.pathFinderOptions.pathingPriority);
        }
        /// <summary>
        /// Called on Start
        /// </summary>
        protected override void Start()
        {
            base.Start();

            //Get unit ref
            _unit = this.GetUnitFacade();
            _steeringInput.unit = _unit;

            //Init other vars
            _lastFramePosition = _unit.position;
            _stopped = true;

            UpdateInput();
        }
        private void CreateModelUnit()
        {
            GameObject unit = this[0].gameObject;
            GameObject modelUnitGO = new GameObject("Model Unit");

            //Start out inactive so all components are added before life cycle begins, i.e. Awake, Start etc.
            modelUnitGO.SetActive(false);
            var collider = modelUnitGO.AddComponent<SphereCollider>();
            collider.isTrigger = true;

            // make sure model unit is kinematic and does not respond to Unity Gravity
            var rigidbody = modelUnitGO.AddComponent<Rigidbody>();
            rigidbody.isKinematic = true;
            rigidbody.useGravity = false;

            // now we need to copy and clone all the relevant components from the group's units to the new model unit (group leader)

            // copy and clone SteerableUnitComponent, if it exists and is enabled
            var steerableComponent = unit.GetComponent<SteerableUnitComponent>();
            if (steerableComponent != null && steerableComponent.enabled)
            {
                var newSteerable = modelUnitGO.AddComponent<SteerableUnitComponent>();
                newSteerable.CloneFrom(steerableComponent);
            }

            //copy and clone IHeightNavigator component
            var heightNav = unit.As<IHeightNavigator>();
            if (heightNav != null)
            {
                var newNav = modelUnitGO.AddComponent(heightNav.GetType()) as IHeightNavigator;
                newNav.CloneFrom(heightNav);
            }

            // copy and clone IDefineSpeed component, if it exists and is enabled
            var speedComp = unit.As<IDefineSpeed>();
            if (speedComp != null)
            {
                var newSpeedComp = modelUnitGO.AddComponent(speedComp.GetType()) as IDefineSpeed;
                newSpeedComp.CloneFrom(speedComp);
            }

            // copy and clone PathOptionsComponent, if it exists and is enabled
            var pathOptions = unit.GetComponent<PathOptionsComponent>();
            if (pathOptions != null && pathOptions.enabled)
            {
                var newPathOptions = modelUnitGO.AddComponent<PathOptionsComponent>();
                newPathOptions.CloneFrom(pathOptions);
            }

            // copy and clone SteerToAlignWithVelocity, if it exists and is enabled
            var steerToAlign = unit.GetComponent<SteerToAlignWithVelocity>();
            if (steerToAlign != null && steerToAlign.enabled)
            {
                var newSteerToAlign = modelUnitGO.AddComponent<SteerToAlignWithVelocity>();
                newSteerToAlign.CloneFrom(steerToAlign);
            }

            // copy and cone PathVisualizer, if it exists and is enabled
            var pathVisualizer = unit.GetComponent<PathVisualizer>();
            if (pathVisualizer != null && pathVisualizer.enabled)
            {
                var newPathVisualizer = modelUnitGO.AddComponent<PathVisualizer>();
                newPathVisualizer.CloneFrom(pathVisualizer);
            }

            // copy and clone SteerForPathComponent, if it exists and is enabled
            var steerForPath = unit.GetComponent<SteerForPathComponent>();
            if (steerForPath != null && steerForPath.enabled)
            {
                var newSteerForPath = modelUnitGO.AddComponent<SteerForPathComponent>();
                newSteerForPath.CloneFrom(steerForPath);
            }

            // copy and clone UnitComponent, if it exists and is enabled
            var unitComp = unit.GetComponent<UnitComponent>();
            if (unitComp != null && unitComp.enabled)
            {
                var newUnitComponent = modelUnitGO.AddComponent<UnitComponent>();
                newUnitComponent.CloneFrom(unitComp);
                newUnitComponent.isSelectable = false;
                newUnitComponent.selectionVisual = null;
                newUnitComponent.enabled = true;
            }

            // copy and clone SteerForVectorFieldComponent, if it exists and is enabled
            var steerForVectorField = unit.GetComponent<SteerForVectorFieldComponent>();
            if (steerForVectorField != null && steerForVectorField.enabled)
            {
                var newSteerForVector = modelUnitGO.AddComponent<SteerForVectorFieldComponent>();
                newSteerForVector.CloneFrom(steerForVectorField);
                newSteerForVector.arrivalRadiusMargin = 0f;
                newSteerForVector.arrivalDistance = 0.2f;
            }

            // copy and clone SteeringController, if it exists and is enabled
            var steeringController = unit.GetComponent<SteeringController>();
            if (steeringController != null && steeringController.enabled)
            {
                modelUnitGO.AddComponent<SteeringController>();
            }

            // copy Apex Component Master
            var componentMaster = unit.GetComponent<ApexComponentMaster>();
            if (componentMaster != null && componentMaster.enabled)
            {
                modelUnitGO.AddComponent<ApexComponentMaster>();
            }

            // active the model unit here at last - also make sure a UnitFacade is made and that it knows of this group
            modelUnitGO.SetActive(true);
            _modelUnit = modelUnitGO.GetUnitFacade();
            _modelUnit.transientGroup = this;

            if (nextNodePosition.HasValue)
            {
                // if there is a path and the next node position is valid, then place the model unit at the same position as the nearest unit in the group compared to the next node position
                Vector3 nearestPos = Vector3.zero;
                float shortestDistance = float.MinValue;
                for (int i = 0; i < this.count; i++)
                {
                    Vector3 pos = this[i].position;
                    float distance = (pos - nextNodePosition.Value).sqrMagnitude;
                    if (distance < shortestDistance)
                    {
                        shortestDistance = distance;
                        nearestPos = pos;
                    }
                }

                _modelUnit.transform.position = nearestPos;
            }
            else
            {
                // if there is no path or the next node position is not valid, just place model unit at the center of the group
                _modelUnit.transform.position = GetGroupCenterOfGravity();
            }

            _modelUnit.transform.forward = this[0].forward;
        }
        /// <summary>
        /// Removes the specified member from this steering group.
        /// Also disposes itself if the last member is removed.
        /// </summary>
        public override void Remove(IUnitFacade member)
        {
            base.Remove(member);

            if (this.count == 0)
            {
                // if the last member was just removed, the group should dispose of and cleanup itself
                CleanupGroup();
            }
            else if (_currentFormation != null && _modelUnit.isAlive)
            {
                SetFormation(_currentFormation);
            }
        }
示例#52
0
 /// <summary>
 /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group.
 /// </summary>
 /// <param name="count">The group's member count.</param>
 /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param>
 /// <param name="unit">The unit to calculate this particular formation position for.</param>
 /// <returns>
 /// A single formation position based on the count, index and the particular unit.
 /// </returns>
 public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit)
 {
     float start = (count - 1) * -0.5f * _formationSpacing;
     return new Vector3(start + (formationIndex * _formationSpacing), 0f, 0f);
 }
        /// <summary>
        /// Gets the desired steering output.
        /// </summary>
        /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param>
        /// <param name="output">The steering output to be populated.</param>
        public override void GetDesiredSteering(SteeringInput input, SteeringOutput output)
        {
            _lastAvoidVector = Vector3.zero;

            var grid = input.grid;
            if (grid == null)
            {
                // if no grid is available, return
                return;
            }

            _unitData = input.unit;
            if (!_unitData.isGrounded)
            {
                // in the air, give up
                return;
            }

            Vector3 selfPos = _unitData.position;
            var selfCell = grid.GetCell(selfPos);
            if (selfCell == null || !selfCell.isWalkable(_unitData.attributes))
            {
                // already within a blocked or missing cell, give up
                return;
            }

            // Find and avoid 4 neighbour cells - if they are blocked
            _neighbours.Clear();
            _neighbours.Add(selfCell.GetNeighbour(-1, 0));
            _neighbours.Add(selfCell.GetNeighbour(0, -1));
            _neighbours.Add(selfCell.GetNeighbour(1, 0));
            _neighbours.Add(selfCell.GetNeighbour(0, 1));

            // the total radius is the unit's radius + the radius margin + half of the current grid's cell size
            float selfRadius = _unitData.radius + radiusMargin + (grid.cellSize / 2f);
            float selfRadiusSqr = selfRadius * selfRadius;
            Vector3 avoidVector = Vector3.zero;

            int neighboursCount = _neighbours.count;
            for (int i = 0; i < neighboursCount; i++)
            {
                var neighbourCell = _neighbours[i];
                if (neighbourCell == null)
                {
                    // ignore missing cells, this is not containment
                    continue;
                }

                var dir = neighbourCell.position.DirToXZ(selfPos);
                float distanceSqr = dir.sqrMagnitude;
                if (distanceSqr < selfRadiusSqr && !neighbourCell.isWalkableFrom(selfCell, _unitData))
                {
                    // sum up a vector comprising of all avoid vectors
                    avoidVector += dir;
                }
            }

            if (avoidVector.sqrMagnitude == 0f)
            {
                return;
            }

            // set the repulsion vector's magnitude to repulsionStrength
            Vector3 steeringVector = avoidVector.normalized * repulsionStrength;
            _lastAvoidVector = steeringVector;
            output.desiredAcceleration = steeringVector;
        }
        /// <summary>
        /// Adds the specified member to this steering group.
        /// Also initializes model unit-based settings, if they haven't already been initialized.
        /// </summary>
        public override void Add(IUnitFacade member)
        {
            base.Add(member);

            // if the group was created using the 'capacity' ctor overload, we must create model unit when the first unit is added
            if (_modelUnit == null)
            {
                CreateModelUnit();
                InitializeModelUnitSettings();

                _groupCog = member.position;
                _currentGrid = GridManager.instance.GetGrid(_groupCog);
            }
        }
        /// <summary>
        /// Gets an avoidance vector.
        /// </summary>
        /// <param name="selfPos">This unit's position.</param>
        /// <param name="currentVelocity">This unit's current velocity.</param>
        /// <param name="normalVelocity">This unit's normalized current velocity.</param>
        /// <param name="unitData">This unit's UnitFacade.</param>
        /// <param name="otherPos">The other unit's position.</param>
        /// <param name="otherVelocity">The other unit's velocity.</param>
        /// <param name="otherData">The other unit's UnitFacade.</param>
        /// <param name="combinedRadius">The combined radius.</param>
        /// <returns>An avoidance vector from the other unit's collision position to this unit's collision position - if a collision actually is detected.</returns>
        private Vector3 GetAvoidVector(Vector3 selfPos, Vector3 currentVelocity, Vector3 normalVelocity, IUnitFacade unitData, Vector3 otherPos, Vector3 otherVelocity, IUnitFacade otherData, float combinedRadius)
        {
            Vector3 avoidDirection = GetAvoidDirectionVector(selfPos, currentVelocity, otherPos, otherVelocity, combinedRadius);
            float avoidMagnitude = avoidDirection.magnitude;
            if (avoidMagnitude == 0f)
            {
                // if there is absolutely no magnitude to the found avoid direction, then ignore it
                return Vector3.zero;
            }

            float vectorLength = combinedRadius * 0.5f;
            if (vectorLength <= 0f)
            {
                // if the units' combined radius is 0, then we cannot avoid
                return Vector3.zero;
            }

            // normalize the avoid vector and then set it's magnitude to the desired vector length (half of the combined radius)
            Vector3 avoidNormalized = (avoidDirection / avoidMagnitude);
            Vector3 avoidVector = avoidNormalized * vectorLength;

            float dotAngle = Vector3.Dot(avoidNormalized, normalVelocity);
            if (dotAngle <= _cosAvoidAngle)
            {
                // the collision is considered "head-on", thus we compute a perpendicular avoid vector instead
                avoidVector = new Vector3(avoidVector.z, avoidVector.y, -avoidVector.x);
            }
            else if (preventPassingInFront && (otherData.determination > unitData.determination) && (Vector3.Dot(otherVelocity, avoidVector) > 0f && Vector3.Dot(currentVelocity, otherVelocity) >= 0f))
            {
                // if supposed to be preventing front-passing, then check whether we should prevent it in this case and if so compute a different avoid vector
                avoidVector = _selfCollisionPos.DirToXZ(selfPos);
            }

            // scale the avoid vector depending on the distance to collision, shorter distances need larger magnitudes and vice versa
            float collisionDistance = Mathf.Max(1f, selfPos.DirToXZ(_selfCollisionPos).magnitude);
            avoidVector *= currentVelocity.magnitude / collisionDistance;

            return avoidVector;
        }
示例#56
0
 /// <summary>
 /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
 /// </summary>
 protected override void OnStartAndEnable()
 {
     base.OnStartAndEnable();
     _unit = this.GetUnitFacade();
 }
        /// <summary>
        /// The path request callback method.
        /// </summary>
        /// <param name="result">The path result.</param>
        /// <param name="unit">This unit's UnitFacade.</param>
        /// <param name="unitPos">This unit's position.</param>
        /// <param name="destination">The destination.</param>
        /// <param name="grid">The grid.</param>
        private void PathRequestCallback(PathResult result, IUnitFacade unit, Vector3 unitPos, Vector3 destination, IGrid grid)
        {
            // if the normal path request fails...
            Action<PathResult> callback = (secondResult) =>
            {
                if (secondResult.status == PathingStatus.Complete)
                {
                    // add the unit's current position to the path
                    var path = result.path;
                    path.Push(unit);

                    FollowPath(path, unit);
                    return;
                }

                // if the second path request fails, there is nothing to do
                _stopped = true;
            };

            // try to request a path from the nearest (from) walkable cell
            Cell fromCell = grid.GetNearestWalkableCell(unitPos, unitPos, true, 1, unit);
            if (fromCell != null)
            {
                QueuePathRequest(fromCell.position, destination, unit, callback);
                return;
            }

            // if that fails, search through all 8 neighbour cells and attempt to request a path from the nearest isWalkable neighbour
            int neighboursCount = _neighbours.count;
            for (int i = 0; i < neighboursCount; i++)
            {
                var neighbourCell = _neighbours[i];
                if (neighbourCell.isWalkable(unit.attributes))
                {
                    QueuePathRequest(neighbourCell.position, destination, unit, callback);
                    return;
                }
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="DefaultSteeringTransientUnitGroup"/> class.
 /// </summary>
 /// <param name="members">The members.</param>
 public DefaultSteeringTransientUnitGroup(IUnitFacade[] members)
     : base(members)
 {
     Initialize();
 }
        /// <summary>
        /// Gets the desired steering output.
        /// </summary>
        /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param>
        /// <param name="output">The steering output to be populated.</param>
        public override void GetDesiredSteering(SteeringInput input, SteeringOutput output)
        {
            _lastContainVector = Vector3.zero;

            if (input.grid == null)
            {
                // if off-grid, exit early
                return;
            }

            _unitData = input.unit;
            if (!_unitData.isGrounded)
            {
                // while in the air, exit early
                return;
            }

            // prepare local variables
            Vector3 selfPos = _unitData.position;
            Vector3 containVector = Vector3.zero;
            float selfHeight = _unitData.basePosition.y;

            // generate positions to the left and right
            Vector3 rightPos = selfPos + (Vector3.right * bufferDistance);
            Vector3 leftPos = selfPos + (Vector3.left * bufferDistance);
            float rightHeight, leftHeight;

            // sample left and right
            bool rightContain = _heightSampler.TrySampleHeight(rightPos, out rightHeight);
            bool leftContain = _heightSampler.TrySampleHeight(leftPos, out leftHeight);

            // if cell is missing or drop is more than allowed drop height or climb is more than allowed climb height, then compute an axis-aligned containment vector
            if (!rightContain || (selfHeight - rightHeight > _heightCaps.maxDropHeight) || (rightHeight - selfHeight > _heightCaps.maxClimbHeight))
            {
                containVector = Vector3.left;
            }
            else if (!leftContain || (selfHeight - leftHeight > _heightCaps.maxDropHeight) || (leftHeight - selfHeight > _heightCaps.maxClimbHeight))
            {
                containVector = Vector3.right;
            }

            // generate positions forward and backwards
            Vector3 forwardPos = selfPos + (Vector3.forward * bufferDistance);
            Vector3 backwardPos = selfPos + (Vector3.back * bufferDistance);
            float forwardHeight, backHeight;

            // sample forward and backwards
            bool forwardContain = _heightSampler.TrySampleHeight(forwardPos, out forwardHeight);
            bool backContain = _heightSampler.TrySampleHeight(backwardPos, out backHeight);

            // if cell is missing or drop is more than allowed drop height or climb is more than allowed climb height, then compute an axis-aligned containment vector
            if (!forwardContain || (selfHeight - forwardHeight > _heightCaps.maxDropHeight) || (forwardHeight - selfHeight > _heightCaps.maxClimbHeight))
            {
                // we need to check whether containVector has a value beforehand, in which case we need to normalize
                containVector = containVector.sqrMagnitude != 0f ? (containVector + Vector3.back).normalized : Vector3.back;
            }
            else if (!backContain || (selfHeight - backHeight > _heightCaps.maxDropHeight) || (backHeight - selfHeight > _heightCaps.maxClimbHeight))
            {
                // we need to check whether containVector has a value beforehand, in which case we need to normalize
                containVector = containVector.sqrMagnitude != 0f ? (containVector + Vector3.forward).normalized : Vector3.forward;
            }

            if (containVector.sqrMagnitude == 0f)
            {
                // no contain vectors to worry about - no containment necessary
                return;
            }

            // Containment vectors are always "full strength"
            Vector3 steeringVector = containVector * input.maxAcceleration;
            _lastContainVector = steeringVector;
            output.desiredAcceleration = steeringVector;
        }
        /// <summary>
        /// Handles portalling.
        /// </summary>
        /// <param name="unitData">This unit's UnitFacade.</param>
        /// <param name="group">This unit's current/old group.</param>
        /// <param name="vectorField">The vector field.</param>
        /// <param name="pathPortalIndex">Index of the portal in the current path.</param>
        /// <param name="grid">The grid.</param>
        private void HandlePortal(IUnitFacade unitData, DefaultSteeringTransientUnitGroup group, IVectorField vectorField, int pathPortalIndex, IGrid grid)
        {
            var portal = _currentPath[pathPortalIndex] as IPortalNode;
            if (portal == null)
            {
                // if the path node that was reported as a portal turns out not to be - return
                return;
            }

            if (object.ReferenceEquals(unitData, group.modelUnit))
            {
                // don't ever let model unit jump portal
                return;
            }

            int groupCount = group.count;
            var to = _currentPath[pathPortalIndex + 1];

            // we consider a portal a "far portal" when the distance between it and its partner is more than the diagonal cell size
            // 'far portal' means that it is NOT considered a grid stitching connector portal
            // Requires that grid stitching portals are always placed adjacent to each other
            float portalNewGroupThreshold = (grid.cellSize * Consts.SquareRootTwo) + 0.1f;
            bool isFarPortal = (portal.position - portal.partner.position).sqrMagnitude > (portalNewGroupThreshold * portalNewGroupThreshold);

            if (isFarPortal)
            {
                // if it is a far portal, we need to make or use the next group
                if (group.nextGroup == null)
                {
                    // new group does not exist yet, so create it and tell the old group about it
                    var groupStrat = GroupingManager.GetGroupingStrategy<IUnitFacade>();
                    if (groupStrat == null)
                    {
                        Debug.Log("No Grouping Strategy has been registered for IUnitFacade");
                        return;
                    }

                    var newGroup = groupStrat.CreateGroup(groupCount) as DefaultSteeringTransientUnitGroup;
                    if (newGroup == null)
                    {
                        return;
                    }

                    group.nextGroup = newGroup;
                    _nextGroup = newGroup;
                }
                else
                {
                    // new group exists, so just use it
                    _nextGroup = group.nextGroup;
                }

                // make sure to remove the unit from the old group
                group.Remove(unitData);
            }

            _isPortalling = true;
            // actually execute the portal
            portal.Execute(
                unitData.transform,
                to,
                () =>
                {
                    _isPortalling = false;

                    if (isFarPortal)
                    {
                        // if it is a far portal, we are supposed to join up with the new group
                        _nextGroup.Add(unitData);
                        unitData.transientGroup = _nextGroup;

                        if (_nextGroup.count == 1)
                        {
                            // let the first unit in the new group be responsible for setting the new group up
                            if (vectorField.destination != null)
                            {
                                // the new group's path starts on the other side of the portal...
                                int pathCount = _currentPath.count;
                                var newPath = new Path(pathCount - (pathPortalIndex + 2));
                                for (int i = pathCount - 1; i >= pathPortalIndex + 2; i--)
                                {
                                    newPath.Push(_currentPath[i]);
                                }

                                // the first member that joins the new group tells the new group to move along the path of the old group
                                Vector3 destination = vectorField.destination.position;
                                if ((to.position - destination).sqrMagnitude > 1f)
                                {
                                    // check though that the destination is not the same as the starting position
                                    _nextGroup.MoveAlong(newPath);
                                }

                                // pass along old group's waypoints to new group
                                if (group.currentWaypoints.count > 0)
                                {
                                    _nextGroup.SetWaypoints(group.currentWaypoints);
                                }

                                // pass along old group's formation to the new group
                                if (group.currentFormation != null)
                                {
                                    _nextGroup.TransferFormation(group.currentFormation, groupCount, group);
                                }
                            }
                        }
                    }
                });
        }