1
0
forked from cgvr/DeltaVR
Files
DeltaVR3DModelGeneration/Assets/_PROJECT/Scripts/ModeGeneration/ShapeDetection/ShapeScanner.cs

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();
}
}