HyCodeYourTale
classpublicPriority 3

PathFollower

com.hypixel.hytale.server.npc.navigation.PathFollower

26

Methods

26

Public Methods

13

Fields

1

Constructors

Constructors

public
PathFollower()

Methods

Public Methods (26)

public
IWaypoint advance(int skip)
@Nullable@Override
public
void clearPath()
public
void computeRejection(Vector3d currentPosition, Vector3d target, MotionController activeMotionController)
public
void executePath(Vector3d currentPosition, MotionController activeMotionController, Steering desiredSteering)
public
boolean freezeWaypoint()
public
IWaypoint getCurrentWaypoint()
@Nullable
public
Vector3d getCurrentWaypointPosition()
@Nullable
public
int getLength()
@Override
public
IWaypoint getNextWaypoint()
@Nullable
public
Vector3d getNextWaypointPosition()
@Nullable
public
Vector3d getPosition()
@Nonnull@Override
public
double getRelativeSpeed()
public
boolean isWaypointFrozen()
public
IWaypoint next()
@Nullable@Override
public
boolean pathInFinalStage()
public
void setBlendHeading(double blendHeading)
public
void setDebugNodes(boolean debugNodes)
public
void setPath(IWaypoint firstWaypoint, Vector3d startPosition)
public
void setPathSmoothing(int pathSmoothing)
public
void setRejectionWeight(double rejectionWeight)
public
void setRelativeSpeed(double relativeSpeed)
public
void setRelativeSpeedWaypoint(double relativeSpeedWaypoint)
public
void setWaypointFrozen(boolean waypointFrozen)
public
void setWaypointRadius(double waypointRadius)
public
boolean shouldSmoothPath()
public
boolean updateCurrentTarget(Vector3d entityPosition, MotionController motionController)

Fields

Protected Fields (13)

protecteddouble blendHeading
protectedIWaypoint currentWaypoint
protecteddouble currentWaypointDistanceSquared
protectedboolean debugNodes
protectedPathFollower.FrozenWaypoint frozenWaypoint
protectedboolean isWaypointFrozen
protectedint pathSmoothing
protecteddouble rejectionWeight
protecteddouble relativeSpeed
protecteddouble relativeSpeedWaypoint
protectedboolean shouldSmoothPath
protecteddouble waypointRadius
protecteddouble waypointRadiusSquared

Related Classes

Source Code

package com.hypixel.hytale.server.npc.navigation;

import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.npc.movement.controllers.ProbeMoveData;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class PathFollower {
   @Nullable
   protected IWaypoint currentWaypoint;
   protected double currentWaypointDistanceSquared;
   protected PathFollower.FrozenWaypoint frozenWaypoint;
   protected boolean isWaypointFrozen;
   protected final Vector3d lastWaypointPosition = new Vector3d();
   protected final Vector3d direction = new Vector3d();
   protected final Vector3d tempVector = new Vector3d();
   protected final Vector3d tempPath = new Vector3d();
   protected final Vector3d projection = new Vector3d();
   protected final Vector3d rejection = new Vector3d();
   protected int pathSmoothing;
   protected double blendHeading;
   protected double relativeSpeed;
   protected double relativeSpeedWaypoint;
   protected double waypointRadius;
   protected double rejectionWeight = 3.0;
   protected double waypointRadiusSquared;
   protected boolean debugNodes = false;
   protected boolean shouldSmoothPath = true;

   public PathFollower() {
   }

   public void setPathSmoothing(int pathSmoothing) {
      this.pathSmoothing = pathSmoothing;
   }

   public double getRelativeSpeed() {
      return this.relativeSpeed;
   }

   public void setRelativeSpeed(double relativeSpeed) {
      this.relativeSpeed = relativeSpeed;
   }

   public void setRelativeSpeedWaypoint(double relativeSpeedWaypoint) {
      this.relativeSpeedWaypoint = relativeSpeedWaypoint;
   }

   public void setWaypointRadius(double waypointRadius) {
      this.waypointRadius = waypointRadius;
      this.waypointRadiusSquared = waypointRadius * waypointRadius;
   }

   public void setDebugNodes(boolean debugNodes) {
      this.debugNodes = debugNodes;
   }

   public boolean shouldSmoothPath() {
      return this.shouldSmoothPath;
   }

   public void setRejectionWeight(double rejectionWeight) {
      this.rejectionWeight = rejectionWeight;
   }

   public void setBlendHeading(double blendHeading) {
      if (blendHeading < 0.0 || blendHeading > 1.0) {
         blendHeading = 0.0;
      }

      this.blendHeading = blendHeading;
   }

   @Nullable
   public IWaypoint getCurrentWaypoint() {
      return this.currentWaypoint;
   }

   @Nullable
   public Vector3d getCurrentWaypointPosition() {
      return this.currentWaypoint == null ? null : this.currentWaypoint.getPosition();
   }

   @Nullable
   public IWaypoint getNextWaypoint() {
      return this.currentWaypoint == null ? null : this.currentWaypoint.next();
   }

   @Nullable
   public Vector3d getNextWaypointPosition() {
      IWaypoint waypoint = this.getNextWaypoint();
      return waypoint == null ? null : waypoint.getPosition();
   }

   public void setPath(IWaypoint firstWaypoint, @Nonnull Vector3d startPosition) {
      this.currentWaypoint = firstWaypoint;
      this.lastWaypointPosition.assign(startPosition);
      this.currentWaypointDistanceSquared = 1.7976931348623157E308;
      this.shouldSmoothPath = true;
      this.isWaypointFrozen = false;
   }

   public void clearPath() {
      this.currentWaypoint = null;
      this.isWaypointFrozen = false;
   }

   public boolean pathInFinalStage() {
      if (this.currentWaypoint == null) {
         return true;
      } else if (this.currentWaypoint.next() != null) {
         return false;
      } else {
         this.freezeWaypoint();
         return true;
      }
   }

   public boolean freezeWaypoint() {
      if (this.currentWaypoint == null) {
         return false;
      } else {
         if (this.frozenWaypoint == null) {
            this.frozenWaypoint = new PathFollower.FrozenWaypoint();
         }

         if (this.currentWaypoint == this.frozenWaypoint) {
            return true;
         } else {
            this.frozenWaypoint.position.assign(this.currentWaypoint.getPosition());
            this.currentWaypoint = this.frozenWaypoint;
            this.isWaypointFrozen = true;
            return true;
         }
      }
   }

   public boolean isWaypointFrozen() {
      return this.isWaypointFrozen;
   }

   public void setWaypointFrozen(boolean waypointFrozen) {
      this.isWaypointFrozen = waypointFrozen;
   }

   public void executePath(@Nonnull Vector3d currentPosition, @Nonnull MotionController activeMotionController, @Nonnull Steering desiredSteering) {
      Vector3d target = this.getCurrentWaypointPosition();
      if (target != null) {
         this.tempVector.assign(target).subtract(currentPosition);
         double length = this.tempVector.length();
         desiredSteering.setMaxDistance(length);
         if (length > this.waypointRadius) {
            this.direction.assign(this.tempVector);
            this.computeRejection(currentPosition, target, activeMotionController);
            this.direction.subtract(this.rejection);
            desiredSteering.setTranslation(this.direction.scale(this.relativeSpeed / length));
         } else {
            if (length > 0.1) {
               this.direction.assign(this.tempVector);
            }

            desiredSteering.setTranslation(this.direction.scale(this.relativeSpeedWaypoint / length));
         }
      }
   }

   public void computeRejection(@Nonnull Vector3d currentPosition, @Nonnull Vector3d target, @Nonnull MotionController activeMotionController) {
      this.tempPath.assign(target).subtract(this.lastWaypointPosition).scale(activeMotionController.getComponentSelector());
      this.tempVector.assign(currentPosition).subtract(this.lastWaypointPosition).scale(activeMotionController.getComponentSelector());
      double dotDD = this.tempPath.squaredLength();
      double dotDP = this.tempPath.dot(this.tempVector);
      this.projection.assign(this.tempPath).scale(dotDP / dotDD);
      this.rejection.assign(this.tempVector).subtract(this.projection).scale(this.rejectionWeight);
   }

   public boolean updateCurrentTarget(@Nonnull Vector3d entityPosition, @Nonnull MotionController motionController) {
      if (this.currentWaypoint == null) {
         return false;
      } else {
         Vector3d waypointPosition = this.currentWaypoint.getPosition();
         double distanceSquared = motionController.waypointDistanceSquared(waypointPosition, entityPosition);
         double projectionLength = 0.0;
         boolean reachedWaypoint;
         if (!(distanceSquared <= 1.0000000000000002E-10) && (!(distanceSquared < 0.01) || !(distanceSquared > this.currentWaypointDistanceSquared))) {
            projectionLength = NPCPhysicsMath.dotProduct(waypointPosition, this.lastWaypointPosition, entityPosition, motionController.getComponentSelector());
            reachedWaypoint = projectionLength < 0.0;
         } else {
            reachedWaypoint = true;
         }

         this.currentWaypointDistanceSquared = distanceSquared;
         if (this.debugNodes) {
            NPCPlugin.get()
               .getLogger()
               .at(Level.INFO)
               .log(
                  "=== Target len=%s before=%s targetdist=%s  proj=%s pos=%s tgt=%s",
                  this.currentWaypoint.getLength(),
                  !reachedWaypoint,
                  Math.sqrt(distanceSquared),
                  projectionLength,
                  Vector3d.formatShortString(entityPosition),
                  Vector3d.formatShortString(waypointPosition)
               );
         }

         if (reachedWaypoint) {
            this.lastWaypointPosition.assign(waypointPosition);
            this.currentWaypoint = this.currentWaypoint.next();
            if (this.currentWaypoint == null) {
               this.isWaypointFrozen = false;
               return false;
            }

            this.currentWaypointDistanceSquared = 1.7976931348623157E308;
            this.shouldSmoothPath = true;
            waypointPosition = this.currentWaypoint.getPosition();
            if (this.blendHeading > 0.0) {
               distanceSquared = motionController.waypointDistanceSquared(waypointPosition, entityPosition);
            }
         }

         motionController.requirePreciseMovement(waypointPosition);
         if (this.blendHeading <= 0.0) {
            return true;
         } else {
            motionController.enableHeadingBlending();
            if (distanceSquared > this.waypointRadiusSquared) {
               return true;
            } else {
               IWaypoint nextWaypoint = this.getNextWaypoint();
               if (nextWaypoint == null) {
                  return true;
               } else {
                  this.tempVector.assign(nextWaypoint.getPosition()).subtract(waypointPosition);
                  distanceSquared = NPCPhysicsMath.projectedLengthSquared(this.tempVector, motionController.getComponentSelector());
                  if (distanceSquared < 0.001) {
                     return true;
                  } else {
                     float yaw = PhysicsMath.headingFromDirection(this.tempVector.x, this.tempVector.z);
                     motionController.enableHeadingBlending((double)yaw, waypointPosition, this.blendHeading);
                     return true;
                  }
               }
            }
         }
      }
   }

   public void smoothPath(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Vector3d position,
      @Nonnull MotionController motionController,
      @Nonnull ProbeMoveData probeMoveData,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      this.shouldSmoothPath = false;
      if (this.pathSmoothing > 0) {
         IWaypoint node = this.currentWaypoint;
         if (node != null) {
            int startLength = node.getLength();

            IWaypoint startNode;
            int skip;
            do {
               startNode = node;
               int length = node.getLength();
               if (length == 1) {
                  skip = 0;
                  break;
               }

               skip = Math.min(this.pathSmoothing, length - 1);
               node = node.advance(skip);
            } while (this.canMoveTo(ref, motionController, position, node.getPosition(), probeMoveData, componentAccessor));

            while (skip > 1) {
               int middleSkip = skip / 2;
               IWaypoint middleNode = startNode.advance(middleSkip);
               if (this.canMoveTo(ref, motionController, position, middleNode.getPosition(), probeMoveData, componentAccessor)) {
                  startNode = middleNode;
                  skip -= middleSkip;
               } else {
                  skip = middleSkip;
               }
            }

            if (this.debugNodes) {
               int l = startNode.getLength();
               NPCPlugin.get()
                  .getLogger()
                  .at(Level.INFO)
                  .log(
                     "=== New Target len=%s skipped=%s pos=%s tgt=%s dist=%s",
                     l,
                     startLength - l,
                     Vector3d.formatShortString(position),
                     Vector3d.formatShortString(startNode.getPosition()),
                     position.distanceTo(startNode.getPosition())
                  );
            }

            this.currentWaypoint = startNode;
            this.currentWaypointDistanceSquared = 1.7976931348623157E308;
         }
      }
   }

   protected boolean canMoveTo(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull MotionController motionController,
      @Nonnull Vector3d position,
      @Nonnull Vector3d targetPosition,
      @Nonnull ProbeMoveData probeMoveData,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      probeMoveData.setPosition(position).setTargetPosition(targetPosition);
      return probeMoveData.canMoveTo(ref, motionController, 9.999999994736442E-8, 0.5, componentAccessor);
   }

   private static class FrozenWaypoint implements IWaypoint {
      protected final Vector3d position = new Vector3d();

      private FrozenWaypoint() {
      }

      @Override
      public int getLength() {
         return 1;
      }

      @Nonnull
      @Override
      public Vector3d getPosition() {
         return this.position;
      }

      @Nullable
      @Override
      public IWaypoint advance(int skip) {
         return null;
      }

      @Nullable
      @Override
      public IWaypoint next() {
         return null;
      }
   }
}