// /******************************************************************************
// * File: Pen.cs
// * Copyright (c) 2023 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
// *
// *
// ******************************************************************************/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
namespace QCHT.Samples.Drawing
{
///
/// Abstract class.
/// Defines drawing tool (Pen)
///
public abstract class Pen : MonoBehaviour
{
private const float GLOBAL_WIDTH_REFERENCE = 0.0015f;
[Flags, Serializable]
public enum DrawingMode
{
None = 0x0,
LeftHand = 0x1,
RightHand = 0x2,
};
///
/// The current drawing mode.
///
[Header("Hands")]
[SerializeField]
protected DrawingMode _drawingMode = DrawingMode.RightHand;
[SerializeField] protected float startDrawingTime = 1f;
[Header("Pointer")]
[SerializeField] protected InputAction isLeftTracked;
[SerializeField] protected PencilPointer pencilLeftPointer;
[SerializeField] protected InputAction isRightTracked;
[SerializeField] protected PencilPointer pencilRightPointer;
[Header("Brush")]
[SerializeField] protected BrushDescriptor defaultBrush;
[SerializeField] protected float defaultWidth = 1f;
[Header("Audio")]
[SerializeField] protected AudioSource audioSource;
///
/// The current used brush.
///
protected BrushDescriptor _brush;
///
/// The current brush width.
///
protected float _width;
///
/// Is currently drawing ?
///
private bool _isLeftDrawing;
private bool _isRightDrawing;
///
/// Stores each pencil lines made by the pen tool.
///
protected readonly Stack _pencilLinesHistory = new Stack();
///
/// Stores each pencil removed historical pencil lines.
///
protected readonly Stack _pencilLinesRedo = new Stack();
///
/// Validation time between pinch and start drawing.
///
private float _downTime;
#region MonoBehaviour Functions
public void Start()
{
SetBrush(defaultBrush);
SetWidth(defaultWidth);
}
public void OnEnable()
{
isLeftTracked.Enable();
isRightTracked.Enable();
}
public void OnDisable()
{
isLeftTracked.Disable();
isRightTracked.Disable();
}
public void Update()
{
UpdatePencil(true, pencilLeftPointer, ref _isLeftDrawing);
UpdatePencil(false, pencilRightPointer, ref _isRightDrawing);
}
#endregion
#region Public Functions
///
/// Sets the brush descriptor for this pen.
///
/// The selected brush descriptor.
public void SetBrush(BrushDescriptor brush)
{
_brush = brush;
var color = _brush.Type == BrushDescriptor.ColorType.Gradient
? _brush.Gradient.Evaluate(1f)
: _brush.Color;
pencilLeftPointer.SetColor(color);
pencilRightPointer.SetColor(color);
if (_brush.LineParticles)
{
pencilLeftPointer.SetLineParticles(Instantiate(_brush.LineParticles).GetComponent());
pencilRightPointer.SetLineParticles(Instantiate(_brush.LineParticles).GetComponent());
}
else
{
pencilLeftPointer.DestroyLineParticles();
pencilRightPointer.DestroyLineParticles();
}
}
///
/// Sets the width of the pencil.
/// It is multiplied by the GLOBAL_WIDTH_REFERENCE in order to friendly scale it.
///
/// The new desired width.
public void SetWidth(float width)
{
_width = width * GLOBAL_WIDTH_REFERENCE;
pencilLeftPointer.SetScale(_width);
pencilRightPointer.SetScale(_width);
}
///
/// Sets the drawing mode.
///
public void SetDrawingMode(DrawingMode drawingMode)
{
_drawingMode = drawingMode;
}
///
/// Undo the last line.
///
public void Undo()
{
if (_pencilLinesHistory.Count == 0)
return;
var line = _pencilLinesHistory.Peek();
if (!line)
return;
line = _pencilLinesHistory.Pop();
line.SetActive(false);
_pencilLinesRedo.Push(line);
}
///
/// Redo the last removed/hidden line.
///
public void Redo()
{
if (_pencilLinesRedo.Count == 0)
return;
var line = _pencilLinesRedo.Pop();
line.SetActive(true);
_pencilLinesHistory.Push(line);
}
///
/// Clear all lines and clear the history.
///
public void Clear()
{
foreach (var line in _pencilLinesHistory)
Destroy(line);
foreach (var line in _pencilLinesRedo)
Destroy(line);
OnPenClear();
}
#endregion
///
/// Performs drawing logic for a given hand.
///
/// Is left hand?
/// The pencil pointer to update.
/// Is hand currently drawing?
private void UpdatePencil(bool isLeft, PencilPointer pencilPointer, ref bool isDrawing)
{
var checkActive = isLeft
? _drawingMode.HasFlag(DrawingMode.LeftHand)
: _drawingMode.HasFlag(DrawingMode.RightHand);
var isTracked = isLeft ? isLeftTracked.IsPressed() : isRightTracked.IsPressed();
if (!checkActive || !isTracked)
{
pencilPointer.Hide();
if (!isDrawing) return;
isDrawing = false;
OnPenUp(isLeft);
pencilPointer.StopLineParticles();
return;
}
pencilPointer.Show();
UpdatePointer(isLeft, pencilPointer);
// Pen down wait before starting to draw
if (!isDrawing && pencilPointer.PenDown.IsPressed())
_downTime += Time.deltaTime;
// Start drawing
if (_downTime >= startDrawingTime)
{
_downTime = 0f;
isDrawing = true;
OnPenDown(isLeft);
pencilPointer.StartLineParticles();
if (!audioSource && !_brush.StartDrawing)
audioSource.PlayOneShot(_brush.StartDrawing);
}
// Pen up
if (isDrawing && !pencilPointer.PenDown.IsPressed())
{
isDrawing = false;
OnPenUp(isLeft);
pencilPointer.StopLineParticles();
}
// Drawing
if (isDrawing)
{
OnPenDrawingUpdate(isLeft);
}
else
{
pencilPointer.Show();
}
}
///
/// Updates the pointer position and scale.
///
private static void UpdatePointer(bool isLeft, PencilPointer pencilPointer)
{
pencilPointer.UpdateScale();
}
#region Abstract Functions
///
/// Called when the pen started drawing.
///
protected abstract void OnPenDown(bool isLeft);
///
/// Called when the pen stopped drawing.
///
protected abstract void OnPenUp(bool isLeft);
///
/// Called each frame when the pen currently drawing.
///
protected abstract void OnPenDrawingUpdate(bool isLeft);
///
/// Called when pen was cleared.
///
protected abstract void OnPenClear();
#endregion
}
}