0
\$\begingroup\$

I am trying to find a way to divert a vector3 position around the surface of a mesh which doesn't have a collider. I have a function (found online) that calculates the closest position on the surface of the mesh but it only works if the position is outside the mesh.

In the diagram below, the red arrow represents whats currently happening. It works great when it's outside the mesh and will give a position on the surface of the mesh, but once it moves forward and enters the mesh, it just keeps walking straight. The green line shows what i would like to happen.

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Is it an option to give the mesh a collider (possibly on an isolated layer so it won't interfere with any other physics stuff you're using)? Being able to use the collider raycasting methods will make this much simpler and more efficient than redoing all this in our own script. \$\endgroup\$ – DMGregory Feb 19 '18 at 13:55
  • \$\begingroup\$ It's an option, i would just prefer to do it without. Do you have any examples that use a collider? \$\endgroup\$ – user7856951 Feb 19 '18 at 15:15
1
\$\begingroup\$

With a collider attached to the object, we can use a method like this to skim along its surface:

void MoveAroundObstacle(
                Vector3 destination, 
                float speed, 
                Collider obstacle,
                float bufferRadius) {

     // How far can we move this frame?
    float range = speed * Time.deltaTime;

    if(obstacle != null) {
        // Check if we hit the obstacle on this move.
        Vector3 direction = destination - transform.position;
        Ray ray = new Ray(transform.position, direction);
        RaycastHit hit;
        if(obstacle.Raycast(ray, out hit, range + bufferRadius)) {
            direction = direction.normalized;

            // Project the direction we want to go onto the surface.
            Vector3 parallel = direction
                             - Vector3.Dot(hit.normal, direction) * hit.normal;

            // Ensure we have a sensible unit vector parallel to the surface.
            if(Mathf.Approximately(parallel.sqrMagnitude, 0f)) {
                 parallel = Quaternion.LookRotation(hit.normal) * Vector3.right;
            } else {
                 parallel = parallel.normalized;
            }

            // Divvy-up our movement for the frame between movement toward the destination,
            // and movement along the surface in the closest direction available.
            float forwardMovement = hit.distance - bufferRadius;
            float sidewaysMovement = Mathf.Max(range - Mathf.Abs(forwardMovement), 0f);

            // Make the move.
            transform.Translate(
                      forwardMovement * direction
                    + sidewaysMovement * parallel,
                      Space.World);
            return;
        }
    }

    // No obstacle in range. Let's use our full movement to travel toward the destination.
    transform.position = Vector3.MoveTowards(
                              transform.position,
                              destination,
                              range);
}

Some things to note:

  • a bufferRadius greater than zero helps keep our object from penetrating into the obstacle visually, and also helps keep our raycast start point slightly outside the object, so we never miss it by starting our scan from inside its skin already.

  • this method uses naive hill-climbing based on the current closest intersection point alone - it doesn't know anything about the global structure of the shape. If the object is mostly convex, this shouldn't pose a problem. But if it has a valley or divot exactly opposite the destination, this following logic can get stuck there.

    (From one side of the valley, it looks like descending deeper into the valley brings us closer to the destination, so we descend until we start to climb the far side, at which point it looks like the closest path to the destination is to turn around and go back down into the valley)

  • This method fires just one ray, assuming that the overall surface behaviour in this region will be well represented by a single ray hit. ie. It posits that the collider doesn't have extremely small crevices & sharp corners that might give a misleading result. The buffer radius gives us some protection against these issues, but it's not bulletproof.

    If you need a more robust result, you can use a SphereCast or other shapecast instead, and fire a secondary cast after computing the move from the first hit to ensure it's not taking us into an adjacent wall.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.