//***********************************************************
// Filename: BallTeleport.cs
// Author: Moritz Kolvenbach, Marco Fendrich
// Last changes: Thursday, 9th of August 2018
// Content: Teleportation with a thrown ball
//***********************************************************
using System.Collections.Generic;
using UnityEngine;
///
/// Ball teleport by throwing an instantiated object from the player's hand
///
public class BallTeleport : MonoBehaviour, IButton
{
// general reference classes for access
private GameObject gameManager;
private Visualiser renderScript;
private Teleport teleportScript;
private ViveNavMesh navMesh;
// list of points within parabola or line being shown to player as indicator for his teleportation
protected List projectionPoints;
// distance in meters between the single points of the optical representation
protected float pointSpacing;
// maximum number of points being calculated until parabola is being stopped
protected int pointCount;
// prefab to be thrown
public GameObject ballPrefab;
// instantiated object while throwing
private GameObject ballObject;
// current state of teleportation
private BallState ballState;
// additional information needed for calculations; details below
private Vector3 lastPosition;
private bool isBallAboveMesh;
private Vector3 teleportDestination;
private Vector3 normalVector;
// reference to controller
private SteamVR_Controller.Device controller;
// for rendering only
protected Vector3 normalisedVelocity = Vector3.down;
protected Vector3 normalisedHitPoint = Vector3.up;
void Start()
{
// get references for variables declared above
gameManager = GameObject.FindWithTag("GameController");
teleportScript = (Teleport)gameManager.GetComponent(typeof(Teleport));
renderScript = (Visualiser)gameManager.GetComponent(typeof(Visualiser));
navMesh = (ViveNavMesh)gameManager.GetComponent(typeof(ViveNavMesh));
projectionPoints = new List(pointCount);
pointSpacing = renderScript.PointSpacing;
pointCount = renderScript.PointCount;
}
void Update()
{
// only running when ball is currently being thrown
if (ballState != BallState.THROWING) return;
// calculate a line downwards as preparation to show the current teleport target position
UpdateProjectionPoints();
// display the calculated points
renderScript.updateRendering(isBallAboveMesh, projectionPoints, normalisedHitPoint, Vector3.down * 10.0F);
}
///
/// Function being called on press by player; checks the state the teleportation is in and either creates a new ball or
/// destroys the old one. Teleports if the ball was above a valid destination
///
/// gameObject of controller
/// ID of controller currently using this function
public void OnButtonDown(GameObject controllerObject, int controllerIdentificator)
{
// if no ball is instantiated, create new one
if (ballState == BallState.NONE)
{
// create ball at controller position
controller = SteamVR_Controller.Input(controllerIdentificator);
ballObject = Instantiate(ballPrefab, controllerObject.transform.position, controllerObject.transform.rotation);
// set the ball to stay static at front of controller
ballObject.GetComponent().isKinematic = true;
ballObject.transform.SetParent(controllerObject.transform);
// set ballstate to a ball being held
ballState = BallState.HELD;
}
// if a ball is currently instantied and was thrown, destroy it and teleport if the position was valid
else if (ballState == BallState.THROWING)
{
// check whether the ball was above a valid destination
navMesh.Linecast(ballObject.transform.position, new Vector3(ballObject.transform.position.x, ballObject.transform.position.y - 5.0f, ballObject.transform.position.z), out isBallAboveMesh, out teleportDestination, out normalVector);
// if so, teleport there
if (isBallAboveMesh)
{
teleportScript.CallTeleport(teleportDestination);
}
// if not, give haptic feedback that input was registered as no other feedback would be given
else
{
// vibrate controller
}
// destroy ball, stop rendering, reset ball state
Destroy(ballObject);
ballState = BallState.NONE;
renderScript.enabled = false;
}
}
///
/// Function being called on release of button by player; releases ball if one is currently being held
///
public void OnButtonUp()
{
// check if an instantiated ball is being held
if (ballState != BallState.HELD) return;
// if so, decouple from controller
ballObject.GetComponent().isKinematic = false;
ballObject.transform.SetParent(null);
// give the ball the controller's speed increased by factor
ballObject.GetComponent().velocity = controller.velocity * 2.0F;
// set ball state to throwing and start rendering position being targeted
ballState = BallState.THROWING;
renderScript.enabled = true;
}
///
/// Sample a bunch of points along a line until you hit gnd. At that point, cut off the parabola
/// Entire calculation is analogue to except with downward velocity
///
private void UpdateProjectionPoints()
{
// turn velocity to work straight down
normalisedVelocity = transform.TransformDirection(Vector3.down);
projectionPoints.Clear();
projectionPoints.Add(ballObject.transform.position);
Vector3 last = ballObject.transform.position;
for (int i = 0; i < pointCount; i++)
{
Vector3 next = new Vector3(last.x, last.y - pointSpacing, last.z);
Vector3 castHit;
Vector3 norm;
bool endOnNavmesh;
if (navMesh.Linecast(last, next, out endOnNavmesh, out castHit, out norm))
{
projectionPoints.Add(castHit);
normalisedHitPoint = norm;
isBallAboveMesh = endOnNavmesh;
return;
}
else
{
projectionPoints.Add(next);
}
last = next;
}
normalisedHitPoint = Vector3.up;
isBallAboveMesh = false;
}
}
///
/// Represents the player's current use of the ball teleport machanic.
///
public enum BallState
{
// The player is not using teleportation right now
NONE,
// The player is holding an instance of the ball prefab
HELD,
// The ball is currently flying and therefore "selecting" a destination
THROWING
}