Interactivity basics

As indicated before, representing our data is not going to be enough. We want the ability to interact with our data and acquire more information about the objects we see standing out.

Obviously, afterwards, we also want the ability to take a couple of these virtual objects, juggle them, shoot them into an improvised virtual hoop, blow them up like a balloon and then get them to explode in a colorful rainbow! But for now, let's focus on selecting them and displaying some data feedback about them.

Let's also make use of the experiment to see whether we can easily position our data along 3 axis. So to start off, I've imported the data again from an OData service and I am representing the data as follows:

  • The height and the size of the spheres represents sales volume of a customer
  • The position along the X-axis represents the risk associated with the customer
  • The position along the Z-axis represents the potential of the customer
  • The color of the sphere represents liability (whether we're liable to be sued by them), where red means "highly probable", grey means "probably" and green means "you never know"

I also represented the axis "potential" and "risk" visually to make things more interpretable. This is how it looks:

graph

The further from the transparent planes representing the dimensions, the higher the values for risk and potential. The higher and bigger the sphere, the bigger the sales volume. I'm not going to describe how to do this in detail (see other posts), but suffice to say that:

  • I've loaded the data in the same way (as the other blog post)
  • I've instantiated the objects mostly in the same way, except that I'm passing along sales volume, risk and potential to the object as a weighted value (a percentage of the highest value)

The script associated with the objects themselves looks like this:

using UnityEngine;
using System.Collections;
public class BP_Attributes_Int : MonoBehaviour
{
        public int partnerID;
        public float lv_salesvolume;
        public float lv_salesvolumeC;
        public float lv_risk;
        public float lv_riskC;
        public float lv_potential;
        public float lv_potentialC;
        public string lv_liability;


        // Use this for initialization
        void Start ()
        {
                // coloring based on attribute "liability"
                switch (lv_liability) {
                case "High": 
                        gameObject.renderer.material.color = Color.red;
                        break;
                case "Medium": 
                        gameObject.renderer.material.color = Color.gray;
                        break;
                case "Low":
                        gameObject.renderer.material.color = Color.green;
                        break;
                }

                // Positioning in 3 dimensions, along "Risk", "SalesVolume" & "Potential"
                gameObject.transform.position = new Vector3 ((lv_riskC * 12f) - 6.00f, 0.5f + (lv_salesvolumeC * 2f), (lv_potentialC * 12f) - 6f);

                // Calculate the size of the spheres based on "SalesVolume"
                gameObject.transform.localScale += Vector3.one * lv_salesvolumeC / 3;
        }

        // Update is called once per frame
        void Update ()
        {
                // Trigger the destruction of all objects based on the gameobject itself
                if (Input.GetKeyDown ("w")) {
                        Destroy (gameObject);
                }     
        }
}

The coloring and the sizing was already explained in other posts. The positioning however is new:

// Positioning in 3 dimensions, along "Risk", "SalesVolume" & "Potential"
                    gameObject.transform.position = new Vector3 ((lv_riskC * 12f) - 6.00f, 0.5f + (lv_salesvolumeC * 2f), (lv_potentialC * 12f) - 6f);

What this does, is positioning the spheres based on 3 coordinates:

  • X: "(lv_riskC * 12f) - 6.00f", which basically means "the weighted risk value (between 0 & 1) multiplied by the available space (12units) minus the start position (-6)", which makes sense if you know that the swimming pool is 12 units big and has its center at position (0,0).
  • Y: "0.5f + (lv_salesvolumeC * 2f)", which means "the weighted sales volume value (between 0 & 1) multiplied by the maximum height of the graph + 0.5f (which is the height of our watery surface)"
  • Z: same as X, but for potential

Now for selecting objects. I created the following script and assigned it to my environment:

using UnityEngine;
using System.Collections;

public class DataInteraction : MonoBehaviour {

    private GameObject loSelectedObject;
    public Material loHighLight; 

    void Update () {
        //Detect the mouse Click
        if (Input.GetMouseButtonDown(0))
        {
            //Find the object being pointed to
            RaycastHit hitInfo = new RaycastHit();
            bool hit = Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo);
            if (hit) 
            {
                Debug.Log("Hit " + hitInfo.transform.gameObject.name);
                loSelectedObject = hitInfo.transform.gameObject;
                // Highlight the selected gameobject
                loSelectedObject.renderer.material = loHighLight;
            } 
        } 
    }
}

The script detects a click of the left mouse button and then casts a ray in its direction from the camera viewpoint, which basically means it shoots a laser from your eyes to the mouse pointer and identifies the object it hits first. If it's a hit, the log is updated, the gameobject is identified and its material is changed to a light emitting one (easy enough to find how to do this on the Unity forums). That shows us the selected object.

To show its attributes we need something else. We need a popup, a 3D popup that can contain text. Recently Unity (4.6) provided a new method to build User Interfaces that does exactly that: https://unity3d.com/learn/tutorials/modules/beginner/ui

To get the popup going I've built a canvas with a panel and some text spaces on it. It has a "world space" rendering mode, meaning it behaves like any other object in the scene. Its positioning is relative to the user, meaning it pops into being in the corner of the user's eye. I then completed the previous script as follows:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class DataInteraction : MonoBehaviour {

    private GameObject loSelectedObject;
    private BP_Attributes_Int loScript; 
    public GameObject goPanel; 
    public Text goContent1; 
    public Text goContent2; 
    public Text goContent3; 
    public Text goContent4;
    public Material loHighLight; 

    void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            RaycastHit hitInfo = new RaycastHit();
            bool hit = Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo);
            if (hit) 
            {
                Debug.Log("Hit " + hitInfo.transform.gameObject.name);
                loSelectedObject = hitInfo.transform.gameObject;
                // Highlight the selected gameobject
                loSelectedObject.renderer.material = loHighLight;
                // Activate the UI
                goPanel.SetActive(true);
                // Shift the UI to the object dimensions
                loScript = loSelectedObject.GetComponent<BP_Attributes_Int>();
                goContent1.text = "Sales Volume:  " + loScript.lv_salesvolume;
                goContent2.text = "Risk:   " + loScript.lv_risk;  
                goContent3.text = "Potential:  " + loScript.lv_potential; 
                goContent4.text = "Liability:  " + loScript.lv_liability; 
            } 
        } 
    }
}

"using UnityEngine.UI;" takes care of the classes needed to work with the new UI. The public objects for gameobjects & texts identify the ones I've already created in the hierarchy and that I've assigned to the script:

script

The first script is used for the loading of data & object instantiation. The second one is the one above, showing the variables containing the gameobjects created in the hierarchy and assigned here.

The only thing left to do is activate the panel (which I've deactivated in the hierarchy first), which is going to pop it into being, and the changing of the text sections. To do that I've first gotten my script from the components of the identified gameobject so that I can extract its attributes.

All together:

View

Object selection by casting laser rays, give the selected object a shininess, popping a UI into being and identifying the dimensional attributes of the objects we're selecting. Groovy!

Here's the trick however, in Virtual Reality this doesn't work all that well, because a mouse pointer is kind of pointless in a 3D world. We need something else, like ... a hand! And euh... to detect where the hand should be, preferably without hitting that hand into the computer screen when wearing the HMD! (because you know... it really hurts)

Comments

comments powered by Disqus