Week 4: Painting in Virtual Reality

giphy.gif

For this week’s exercise we built a virtual reality painting app like Tilt Brush, but not as fancy, for with the Oculus Rift and touch controllers. The SteamVR interaction library makes it easy to pair buttons and triggers on the hand controllers to functions in your project’s code. In my sketch, touching my “brush” to one of the cubes allows me to paint in 3D space with that same color when I squeeze my controller’s trigger. Here’s an outline of the setup and my code.

Objects

  1. I started by adding objects to my scene’s hierarchy. Adding the SteamVR Player object to the Hierarchy menu and disabling the default main camera allows the Rift-wearing user to become the camera instead.

  2. After that I created a basic 3D plane to which I applied the SteamVR Teleport Area Script. I also added the SteamVR Teleporting object to the Hierarchy, and these items in place allow me to move around the plane using my hand controllers.

  3. Then I added a 3D Plane with its own material (added to Mesh of the Mesh Collider component) to sit just below the Teleporting plane to define that area with some color.

  4. Next I created my color menu with three cubes—Red, Green, and Blue, each with their own material (attached to the Mesh Renderer component). I parented (is this the right terminology?) these cubes to my player’s LeftHand (Player > SteamVRObjects > LeftHand) such that when engaged in VR, they follow the movements of my left controller. My menu travels with me—thanks to Terrick for that idea!

  5. I also created a sphere (my “brush”) to which I parented to my player’s RightHand. Using the SteamVR Behaviour_Boolean (Script) I set the GrabGrip action (squeezing the controller’s trigger) to the Draw() function of my ControllerHander script (see below).

  6. After that I created was a sphere of the same size, which I converted into a Prefab in anticipation of instantiating it multiple times as the “paint” of my brush strokes. 

Code, there are three scripts:

BrushHandler is attached to my brush and sets up a public color variable called currentColor. This does not mirror the actual color of my sphere, however, which I gave its own material.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BrushHandler : MonoBehaviour 
{
    public Color currentColor;
}

ColorSelector is attached to each of the color menu’s cubes and does two things: creates a public color variable that matches the color its cube and contains an OnTriggerEnter function that sets the value of the brush’s currentColor variable to its own color.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColorSelector : MonoBehaviour {

    public Color myColor;

    private void OnTriggerEnter(Collider other)
    {
        FindObjectOfType<BrushHandler>(). currentColor = myColor;
    }
}

ControllerHandler is attached my RightHand controller and create a public GameObject to which I added my DrawingSphere Prefab (see above). My Draw() function, which is called when I squeeze the trigger of my RightHand controller, gets the value of the currentColor variable associated with my brush, instantiates the DrawingSphere object, adds that object to a List, assigns the color to it, and then positions it at the same location in space as my hand. If the number of spheres exceeds a threshold, then they are removed from the list and deleted so as not to bog down the computer’s memory with thousands upon thousands of game objects.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ControllerHandler : MonoBehaviour {

    public GameObject drawingObject;
    private Color newColor;
    public List<GameObject> paintBalls;

    public void Start()
    {
        paintBalls = new List<GameObject>();
    }

    public void Update()
    {
        if (paintBalls.Count > 500)
        {
            Destroy(paintBalls[0]);
            paintBalls.RemoveAt(0);
        }
    }

    public void Draw()
    {
      newColor = FindObjectOfType<BrushHandler>().currentColor;;
      GameObject drawnObject = Instantiate(drawingObject);
      paintBalls.Add(drawnObject);
      drawnObject.GetComponent<MeshRenderer>().material.color = newColor;
      drawnObject.transform.position = transform.position;
    }
}

Next Steps
To improve the user’s experience, I would update the material of the brush to match the color of the menu item it selects. Aaand, after Igal taught us about pure functions and state machines, try to incorporate those in projects using C# and other languages.