/// <summary> /// Handles the situation where there is a missing vector from the vector field. /// </summary> /// <param name="selfCell">This unit's current cell.</param> /// <param name="vectorField">The vector field.</param> /// <param name="input">The steering input.</param> /// <param name="output">The steering output.</param> private void HandleMissingVectorFromField(Cell selfCell, IVectorField vectorField, SteeringInput input, SteeringOutput output) { var unit = input.unit; Vector3 unitPos = unit.position; // find all (up to) 8 neighbours _neighbours.Clear(); input.grid.GetConcentricNeighbours(selfCell, 1, _neighbours); // sort the neighbours depending on their distance to the unit, i.e. nearest cell neighbours first _cellComparer.compareTo = unitPos; _neighbours.Sort(_cellComparer); // loop through cell neighbours and try to escape to the nearest one that is walkable and has a vector field cell int neighboursCount = _neighbours.count; for (int i = 0; i < neighboursCount; i++) { var neighbour = _neighbours[i]; Vector3 neighbourPos = neighbour.position; if (neighbour.IsWalkableWithClearance(unit) && vectorField.GetFieldCellAtPos(neighbour).direction.sqrMagnitude != 0f && neighbourPos.y <= unitPos.y) { // if the neighbour cell is walkable, has a vector field vector and is at a lower or same height as the unit output.maxAllowedSpeed = unit.maximumSpeed; output.desiredAcceleration = Seek(neighbourPos, input); return; } } // only request path if we can't find a neighbouring cell to escape to, if there is a valid destination and if the unit is actually movable if (unit.isMovable && vectorField.destination != null) { RequestPath(vectorField.destination.position, input); } }
/// <summary> /// The first order approximation as seen here (infinitesimal current loop): /// https://en.wikipedia.org/wiki/Magnetic_dipole#External_magnetic_field_produced_by_a_magnetic_dipole_moment /// The magConstant is also known as the permeability. /// Griffiths 5.87 /// </summary> public static IVectorField MagneticDipole(Vector3 magMoment, double magConstant) { Vector3 constantVectOne = (-magConstant / (4 * Math.PI)) * magMoment; IVectorField termOne = FieldExtensions.SphericalScalarField(1.0, -3).Multiply(constantVectOne); double constantTwo = (magConstant / (4 * Math.PI)) * 3; Func <Vector3, Vector3> termTwoFunc = r => Math.Pow(r.Magnitude, -5) * (magMoment * r) * r; IVectorField termTwo = new CustomVectorField(termTwoFunc); return(termOne.Add(termTwo)); }
/// <summary> /// The first order approximation as seen here (infinitesimal seperation): /// https://en.wikipedia.org/wiki/Electric_dipole_moment#Potential_and_field_of_an_electric_dipole /// The elecConstant is the permitivity /// See griffiths 3.104 /// </summary> public static IVectorField ElectricDipole(Vector3 elecMoment, double elecConstant) { if (elecConstant <= 0) { throw new ArgumentException(nameof(elecConstant) + " must be positive"); } Vector3 constantVectOne = (-1 / (4 * Math.PI * elecConstant)) * elecMoment; IVectorField termOne = FieldExtensions.SphericalScalarField(1.0, -3).Multiply(constantVectOne); double constantTwo = (3 / (4 * Math.PI * elecConstant)); Func <Vector3, Vector3> termTwoFunc = r => Math.Pow(r.Magnitude, -3) * (elecMoment * r.UnitDirection) * r.UnitDirection; IVectorField termTwo = new CustomVectorField(termTwoFunc); return(termOne.Add(termTwo)); }
public void SetPath(IVectorField vectorField) { Transformer = vectorField.Transformer; var length = vectorField.Transformer.GridSize.X * vectorField.Transformer.GridSize.Y; if (Vectors == null || Vectors.Length != length) { Vectors = new Array2D <Vector2>(vectorField.Transformer.GridSize.X, vectorField.Transformer.GridSize.Y); } for (var i = 0; i < length; i++) { Vectors[i] = vectorField[i]; } }
private void CleanupGroup() { // null vector field and everything else _vectorField = null; StopInternal(); // remove the group from load balancer NavLoadBalancer.steering.Remove(this); if (_modelUnit != null) { // destroy the model unit GameObject.Destroy(_modelUnit.gameObject, 0.1f); } }
private void SetVectorFieldPath() { _stopped = false; this.hasArrived = false; _announcedEndOnce = false; if (_vectorField != null) { // if the group has a vector field, set new path on it _vectorField.SetNewPath(_currentPath); } else { // if the group does not have a vector field - create one _vectorField = GameServices.vectorFieldManager.CreateVectorField(this, _currentPath); } }
/// <summary> /// Creates a vector field using the VectorFieldOptions exposed on this component. /// </summary> /// <param name="group">The transient unit group.</param> /// <param name="path">The path.</param> /// <returns>A new vector field, or null on error</returns> public IVectorField CreateVectorField(TransientGroup <IUnitFacade> group, Path path) { IVectorField newField = null; switch (vectorFieldOptions.vectorFieldType) { case VectorFieldType.FullGridField: { newField = new FullGridVectorField(group, path, vectorFieldOptions); break; } case VectorFieldType.ProgressiveField: { newField = new ProgressiveVectorField(group, path, vectorFieldOptions); break; } case VectorFieldType.CrossGridField: { newField = new CrossGridVectorField(group, path, vectorFieldOptions); break; } default: case VectorFieldType.FunnelField: { newField = new FunnelVectorField(group, path, vectorFieldOptions); break; } } if (newField == null) { return(null); } newField.Initialize(); return(newField); }
public Problem(IFiniteElementSpace finiteElementSpace, Dictionary <int, IVectorField> boundaryConditions, BilinearForm bilinearForm, IVectorField rightHandSide, ISolver solver = null, IQuadrature quadrature = null) { this.finiteElementSpace = finiteElementSpace; this.boundaryConditions = boundaryConditions; this.bilinearForm = bilinearForm; this.rightHandSide = rightHandSide; var dim = rightHandSide.Dimension; var mismatch = boundaryConditions.Values .Any(condition => condition.Dimension != dim); if (mismatch) { throw new ArgumentException("Dimension mismatched."); } // TODO: Validate bilinear form. this.solver = solver ?? new ConjugateGradient(1e-6); this.quadrature = quadrature ?? new GaussianQuadrature(); }
/// <summary> /// A basic force field that has no dependence on the object the force is acting on. /// </summary> public static ForceField BasicForce(IVectorField forceField) => new ForceField(forceField);
public ClampedVectorField(IVectorField vectorField, Func<Vector3, bool> clampFunction) { UnderlyingVectorField = vectorField; ClampFunction = clampFunction; }
public ProductVectorField(IVectorField origField, double constantMultiplier) : this(origField, () => constantMultiplier) { }
public ProductVectorField(IVectorField vectorField, Generator<double> multiplierGen) { this.multiplierGen = multiplierGen; UnderlyingVectorField = vectorField; }
/// <summary> /// Adds this vector field to another. /// </summary> public static SumVectorField Add(this IVectorField f, IVectorField other) => new SumVectorField(f, other);
/// <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); } } } } }); }
/// <summary> /// Directly applies the raw field as a force and applies zero torque. /// </summary> public ForceField(IVectorField rawField) : this(rawField, ForceFieldFactory.DirectApplier) { }
public ForceField(IVectorField rawField, ForceApplicationFunc forApplFunc, ForceApplicationFunc torqueApplFunc) { RawField = rawField; ForceApplicationFunc = forApplFunc; TorqueApplicationFunc = torqueApplFunc; }
public TranslatedVectorField(IVectorField underlyingVectField, Generator <Vector3> translationGen) { UnderlyingVectorField = underlyingVectField; this.translationGen = translationGen; }
/// <summary> /// Always applies zero torque and applies the specified force. /// </summary> public ForceField(IVectorField rawField, ForceApplicationFunc applFunc) : this(rawField, applFunc, ForceFieldFactory.NullApplier) { }
public static ForceField Drag(IVectorField windField) => new ForceField(windField, DragForceApplier);
public SumVectorField(IVectorField a, IVectorField b) { A = a; B = b; }
public ClampedVectorField(IVectorField vectorField, Func <Vector3, bool> clampFunction) { UnderlyingVectorField = vectorField; ClampFunction = clampFunction; }
/// <summary> /// Returns a new vector field with the origin moved to a specified position. /// </summary> public static TranslatedVectorField Translate(this IVectorField f, Vector3 translation) => new TranslatedVectorField(f, () => translation);
/// <summary> /// Multiplies this vector field by a constant value. /// </summary> public static ProductVectorField Multiply(this IVectorField f, double mult) => new ProductVectorField(f, mult);
/// <summary> /// Returns a new vector field that is 'clamped' outside a specified volume of influence. /// When clamped the value of the field is zero. /// </summary> public static ClampedVectorField Clamp(this IVectorField f, Func <Vector3, bool> clampFunction) => new ClampedVectorField(f, clampFunction);
/// <summary> /// Handles the situation where there is a missing vector from the vector field. /// </summary> /// <param name="selfCell">This unit's current cell.</param> /// <param name="vectorField">The vector field.</param> /// <param name="input">The steering input.</param> /// <param name="output">The steering output.</param> private void HandleMissingVectorFromField(Cell selfCell, IVectorField vectorField, SteeringInput input, SteeringOutput output) { var unit = input.unit; Vector3 unitPos = unit.position; // find all (up to) 8 neighbours _neighbours.Clear(); input.grid.GetConcentricNeighbours(selfCell, 1, _neighbours); // sort the neighbours depending on their distance to the unit, i.e. nearest cell neighbours first _cellComparer.compareTo = unitPos; _neighbours.Sort(_cellComparer); // loop through cell neighbours and try to escape to the nearest one that is walkable and has a vector field cell int neighboursCount = _neighbours.count; for (int i = 0; i < neighboursCount; i++) { var neighbour = _neighbours[i]; Vector3 neighbourPos = neighbour.position; if (neighbour.isWalkable(unit.attributes) && vectorField.GetFieldCellAtPos(neighbour).direction.sqrMagnitude != 0f && neighbourPos.y <= unitPos.y) { // if the neighbour cell is walkable, has a vector field vector and is at a lower or same height as the unit output.maxAllowedSpeed = unit.maximumSpeed; output.desiredAcceleration = Seek(neighbourPos, input); return; } } // only request path if we can't find a neighbouring cell to escape to, if there is a valid destination and if the unit is actually movable if (unit.isMovable && vectorField.destination != null) { RequestPath(vectorField.destination.position, input); } }
/// <summary> /// Returns a new vector field with the origin moved by a specified generation function. /// </summary> public static TranslatedVectorField Translate(this IVectorField f, Generator <Vector3> translation) => new TranslatedVectorField(f, translation);
public ProductVectorField(IVectorField vectorField, Generator <double> multiplierGen) { this.multiplierGen = multiplierGen; UnderlyingVectorField = vectorField; }
public TranslatedVectorField(IVectorField underlyingVectField, Generator<Vector3> translationGen) { UnderlyingVectorField = underlyingVectField; this.translationGen = translationGen; }
/// <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); } } } } }); }