forked from cgvr/DeltaVR
236 lines
7.9 KiB
C#
236 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.XR.Interaction.Toolkit;
|
|
|
|
|
|
[Serializable]
|
|
public class BoolRow
|
|
{
|
|
public List<bool> cells = new();
|
|
|
|
public BoolRow(List<bool> cells)
|
|
{
|
|
this.cells = cells;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class ShapeScannerConfiguration
|
|
{
|
|
public int columns = 6;
|
|
public float requiredCorrectPercentage = 80.0f;
|
|
public List<BoolRow> rows = new();
|
|
public ShapeScannerConfiguration(int columns, float requiredCorrectPercentage, List<BoolRow> rows)
|
|
{
|
|
this.columns = columns;
|
|
this.requiredCorrectPercentage = requiredCorrectPercentage;
|
|
this.rows = rows;
|
|
}
|
|
}
|
|
|
|
|
|
public class ShapeScanner : MonoBehaviour
|
|
{
|
|
public delegate void OnShapeScannerCompletedDelegate();
|
|
public event OnShapeScannerCompletedDelegate OnShapeScannerCompleted;
|
|
|
|
[Header("Static References")]
|
|
public List<ShapeScannerConfiguration> configurations = new List<ShapeScannerConfiguration>();
|
|
public ShapeScannerRay rayPrefab;
|
|
public Transform raySpawnCorner1;
|
|
public Transform raySpawnCorner2;
|
|
public Transform rayParent;
|
|
public TextMeshProUGUI currentConfigurationDisplay;
|
|
public TextMeshProUGUI correctPercentageDisplay;
|
|
public XRInteractionManager interactionManager;
|
|
|
|
[Header("Materials")]
|
|
public Material requiredAndActive;
|
|
public Material requiredAndPassive;
|
|
public Material notRequiredAndActive;
|
|
public Material notRequiredAndPassive;
|
|
|
|
[Header("On Completion")]
|
|
public LineRenderer lineRenderer;
|
|
public Transform lineStart;
|
|
public Transform lineEnd;
|
|
public MeshRenderer activatableRenderer;
|
|
public Material activatableActiveMaterial;
|
|
public HingeJoint doorJoint;
|
|
public Collider doorInvisibleWall;
|
|
|
|
private List<ShapeScannerRay> existingRays;
|
|
private float raySpawnDistanceX;
|
|
private float raySpawnDistanceZ;
|
|
private int currentConfiguration;
|
|
private int rayCount;
|
|
private int correctRayStates;
|
|
private bool isCompleted;
|
|
|
|
|
|
// Start is called before the first frame update
|
|
void Start()
|
|
{
|
|
existingRays = new List<ShapeScannerRay>();
|
|
raySpawnDistanceX = raySpawnCorner2.localPosition.x - raySpawnCorner1.localPosition.x;
|
|
raySpawnDistanceZ = raySpawnCorner2.localPosition.z - raySpawnCorner1.localPosition.z;
|
|
|
|
lineRenderer.enabled = false;
|
|
isCompleted = false;
|
|
currentConfiguration = 0;
|
|
InitializeConfiguration(configurations[0]);
|
|
}
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
if (lineRenderer.enabled)
|
|
{
|
|
lineRenderer.SetPosition(0, lineStart.position);
|
|
lineRenderer.SetPosition(1, lineEnd.position);
|
|
}
|
|
}
|
|
|
|
private void InitializeConfiguration(ShapeScannerConfiguration configuration)
|
|
{
|
|
// Recreate all existing rays
|
|
foreach (ShapeScannerRay ray in existingRays)
|
|
{
|
|
Destroy(ray.gameObject);
|
|
}
|
|
existingRays.Clear();
|
|
|
|
int rayRowCount = configuration.rows.Count;
|
|
for (int i = 0; i < rayRowCount; i++)
|
|
{
|
|
float rayPosX = raySpawnCorner1.localPosition.x + i * raySpawnDistanceX / (rayRowCount - 1);
|
|
for (int j = 0; j < rayRowCount; j++)
|
|
{
|
|
// Local position
|
|
float rayPosZ = raySpawnCorner1.localPosition.z + j * raySpawnDistanceZ / (rayRowCount - 1);
|
|
Vector3 rayPos = new Vector3(rayPosX, 0, rayPosZ);
|
|
ShapeScannerRay ray = Instantiate(rayPrefab, rayParent);
|
|
ray.transform.localPosition = rayPos;
|
|
existingRays.Add(ray);
|
|
|
|
bool rayCollisionRequired = configuration.rows[i].cells[j];
|
|
if (rayCollisionRequired)
|
|
{
|
|
ray.Initialize(this, rayCollisionRequired, requiredAndActive, requiredAndPassive);
|
|
}
|
|
else
|
|
{
|
|
ray.Initialize(this, rayCollisionRequired, notRequiredAndActive, notRequiredAndPassive);
|
|
}
|
|
}
|
|
}
|
|
// Count total rays and required collision rays
|
|
rayCount = configuration.rows.SelectMany(row => row.cells).Count();
|
|
correctRayStates = configuration.rows.SelectMany(row => row.cells).Count(cell => !cell);
|
|
UpdateDisplay(calculateCorrectPercentage());
|
|
}
|
|
|
|
private void DestroyCollidingObjects()
|
|
{
|
|
HashSet<GameObject> allCollidingObjects = new HashSet<GameObject>();
|
|
foreach (ShapeScannerRay ray in existingRays)
|
|
{
|
|
allCollidingObjects.UnionWith(ray.GetCollidingObjects());
|
|
}
|
|
foreach (GameObject collidingObject in allCollidingObjects)
|
|
{
|
|
// Release player holding it
|
|
var grab = collidingObject.GetComponent<XRGrabInteractable>();
|
|
if (grab != null && grab.isSelected)
|
|
{
|
|
var interactor = grab.firstInteractorSelecting; // the hand/controller currently holding it
|
|
if (interactor != null)
|
|
{
|
|
// Transfer ownership: tell the manager to stop the selection
|
|
interactionManager.SelectExit(interactor, grab);
|
|
}
|
|
}
|
|
collidingObject.transform.position = Vector3.zero;
|
|
Destroy(collidingObject);
|
|
}
|
|
}
|
|
|
|
private float calculateCorrectPercentage()
|
|
{
|
|
return Mathf.RoundToInt((float)correctRayStates / rayCount * 100);
|
|
}
|
|
|
|
public void IncrementCorrectRayCount()
|
|
{
|
|
correctRayStates++;
|
|
float correctPercentage = calculateCorrectPercentage();
|
|
UpdateDisplay(correctPercentage);
|
|
if (isCompleted)
|
|
{
|
|
return;
|
|
}
|
|
if (correctPercentage >= configurations[currentConfiguration].requiredCorrectPercentage)
|
|
{
|
|
UpdateCurrentConfigurationDisplay(currentConfiguration + 1);
|
|
if (currentConfiguration + 1 < configurations.Count)
|
|
{
|
|
AudioManager.Instance.PlayAttachedInstance(FMODEvents.Instance.ShapeScannerSuccess, gameObject);
|
|
DestroyCollidingObjects();
|
|
currentConfiguration++;
|
|
InitializeConfiguration(configurations[currentConfiguration]);
|
|
} else
|
|
{
|
|
AudioManager.Instance.PlayAttachedInstance(FMODEvents.Instance.ShapeScannerSuccess, gameObject);
|
|
DestroyCollidingObjects();
|
|
// Initialize configuration with all rays requiring collision
|
|
List<BoolRow> rows = Enumerable.Repeat(new BoolRow(Enumerable.Repeat(false, 6).ToList()), 6).ToList();
|
|
ShapeScannerConfiguration configuration = new ShapeScannerConfiguration(6, 100, rows);
|
|
InitializeConfiguration(configuration);
|
|
OnCompleteAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DecrementCorrectRayCount()
|
|
{
|
|
correctRayStates--;
|
|
UpdateDisplay(calculateCorrectPercentage());
|
|
}
|
|
|
|
private void UpdateDisplay(float percentage)
|
|
{
|
|
correctPercentageDisplay.text = Mathf.Round(percentage).ToString() + " %";
|
|
}
|
|
|
|
private void UpdateCurrentConfigurationDisplay(int confNumber)
|
|
{
|
|
currentConfigurationDisplay.text = confNumber.ToString() + " / " + configurations.Count;
|
|
}
|
|
|
|
private void OnCompleteAll()
|
|
{
|
|
isCompleted = true;
|
|
|
|
// Change card reader color to green
|
|
activatableRenderer.material = activatableActiveMaterial;
|
|
|
|
// Enable door to be opened
|
|
JointLimits doorJointLimits = doorJoint.limits;
|
|
doorJointLimits.min = -90f;
|
|
doorJointLimits.max = 90f;
|
|
doorJoint.limits = doorJointLimits;
|
|
|
|
// Disable invisible wall
|
|
doorInvisibleWall.enabled = false;
|
|
|
|
// Create line renderer from shape scanner to door card reader
|
|
lineRenderer.enabled = true;
|
|
|
|
OnShapeScannerCompleted?.Invoke();
|
|
}
|
|
}
|