Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
michael_vangeet2
Discoverer

With Virtual Reality building up steam (Oculus, Hololens, Samsung Gear, crystal cove, Morpheus) it's becoming easier and easier to get tempted into experimenting to see if we can use it for business applications.

The idea here is to see how difficult it would be to consume SAP data in virtual reality and render it into objects, so we can analyze the data in more dimensions than the flat ones we typically leverage on a screen or paper.

The objective of my little experiment is to generate a bubble chart in virtual reality (just to see if we can do it).


We should: 

  • Deliver an OData service describing business partners by a couple of dimensions
  • Consume the OData service in a virtual reality application.
    • To deliver the application I'm using  Unity (there's a free version!)
    • To see the data in virtual reality,I'm using  Oculus (buy one, it's awesome!)
  • Instantiate our data by interpreting the service & generating spheres
    • Their size would represent sales volume
    • Their color would represent risk
  • Structure the data to make it interpretable
    • By risk
    • By potential

The OData service

If you frequent forums like these, you can probable figure out how to do this yourself, so I'm not going to describe it in detail. Suffice to say I've got a service set up that provides business partner data in the following form:

A nice OData service that delivers thousands of business partners, their sales volume, risk (low/medium/high), potential (low/medium/high) and location (haven't figured out what to do with this yet). Any SAP Netweaver system can deliver this now that we've got Gateway. You could host this on a BW system, or as I have done, straight on the operational system (in my case: SAP CRM).

VR environment

This is where game development merges with business application development. For the uninitiated, this might be a little tricky, but honestly it is a lot easier than you might expect (IT always is, isn't it). We need:

  • An environment to walk around in and check out our data
  • The person walking around and doing the checking out
  • Some light so he can actually see something
  • Some reference points, so we don't get lost

Translated in Unity, that means:

  • We need a plane in sphere with inverted normals (so we can see it)
  • A first person. This guy does the walking and the looking around, which is available in standard Unity. There's also an API to integrate with Oculus, which is just another "first person" object but with 2 camera viewpoints instead of one.
  • A directional light in the sphere (our sun basically)
  • For our reference point, I've got a pool set up. That's right, a pool! (the water is just mesmerizing in VR!)

It looks like this:

A guy on a plane in a sphere with a light staring at a pool: great going so far.

When plugging in the API from Oculus (put on the Oculus HMD and you can start feeling dizzy already!), it looks like this:

A guy on a plane in a sphere with a light staring at a pool with goggles on: awesome!

Now to get some data in it


This is where Unity scripting comes into play. Unity offers a choice here. I've gone with C# (easier interaction with external libraries).

We need 2 scripts:

  • One to capture the user event, load and instantiate our data.
  • One associated with the objects themselves to provide them with color, size and location (and to destroy them of course).

First script

The first script is associated with one of the objects in the environment (the pool!) or the person itself. In the "Update" routine of Unity (called every couple of milliseconds), we detect user interaction. In this case, when the user taps the "h" button:


// Instantiate based on external data
if (Input.GetKeyDown ("h")) {
StartCoroutine ("LoadOData");
}









That starts a coroutine (separate thread basically) to load the data:


IEnumerator LoadOData() {
    //Load JSON data from a URL
    string url = "file://c:/test/Data";
    WWW BPdata = new WWW(url);
    yield return BPdata;
    if (BPdata.error == null)
    {
        //Sucessfully loaded the JSON string
        Debug.Log("Loaded following JSON string" + BPdata.text);
        //Process the data in JSON file
        ProcessData(BPdata.text);
    }
    else
    {
        Debug.Log("ERROR: " + BPdata.error);
    }
}









This gets us our data. I'm loading it from a local file (still got some CORS issues to figure out with the OData service), which is just a dump of the service data in JSON format. The code loads that data in a coroutine and pushes it as a string in a processor. The processor looks like this:


private void ProcessData(String JsonString)
{
    JsonData BpString= JsonMapper.ToObject(JsonString);
    Debug.Log (BpString["d"]["results"].Count);
    float minVolume = 0;
    float maxVolume = 0;
    for (int i = 0; i<BpString["d"]["results"].Count; i++)
    { if ( minVolume == 0)
        { minVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
        if ( int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString()) < minVolume )
        { minVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
        if ( int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString()) > maxVolume )
        { maxVolume = float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());}
    }
  for(int i = 0; i<BpString["d"]["results"].Count; i++)
    {
        Vector3 pos = new Vector3(UnityEngine.Random.Range(-6.00F,6.00F),0.5f,UnityEngine.Random.Range(-6.00F,6.00F));
        GameObject ball = Instantiate(myInstBall, pos, Quaternion.identity) as GameObject;
        ball.name = "Ball" + i;
        MyScript = ball.GetComponent<BallAttributes_C>();
        MyScript.partnerID = int.Parse(BpString["d"]["results"][i]["Partner"].ToString());
        MyScript.salesVolume = int.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString());
        MyScript.salesVolumeC = (float.Parse(BpString["d"]["results"][i]["Salesvolume"].ToString())/ maxVolume );
        MyScript.risk = BpString["d"]["results"][i]["Risk"].ToString();
    }
}










At the start of the routine, I'm leveraging a mapper of an external library (LITJson). It allows me to flexibly address the objects.

Before instantiating I'm determining the minimum and maximum sales volume of all business partners. There's no knowing which figures to expect, so I'm rendering the sphere's size in reference to a minimum and maximum, which is the same thing as what MS excel is doing when rendering a graph when you think of it.

I'm then looping over all objects (business partners) and instantiating a prefab which I've created before. The prefab is no more than a sphere that I've defined as a prefab and which I've assigned to the variable "myInstBall". I'm giving the sphere a random XYZ coordinate in a range between -6.00 and 6.00 (which just defines an area: the pool). I'm then retrieving a script (our second one) that I've assigned to the prefab and which is instantiated along with the spheres. The script contains the variables of the business partners that (the value of which) I'm passing to it.

Easy enough. This gives us our data in the form of spheres, floating around in our virtual space:

3D Object instantiation in a virtual room based on external JSON data from an OData service! Hurray!

Second script

As you'll notice, the spheres have a color and a size. I'm using the second, object-dependent script to change the appearance of the spheres based on the values of the variables I've passed along during instantiation.

The color is changed as follows:


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










The size like this:


if (salesVolumeC != 0) {
     gameObject.transform.localScal += Vector3.one * salesVolumeC;
}










That gives us a 3D bubble chart, representing sales volume (as the size of the sphere) and risk (as the color). Put the HMD on and you can (literally) step into your data. Nice as this is, we should take it a little further

Structuring the data

Having our spheres confronts us with the reason why 2D representation is so useful. Interpretation of what we see becomes trickier in 3D. So let's see if we can structure our data a little bit better:

What I've done here is to structure the data in 9 sections representing the cross-sections of risk (Low/Medium/High) and potential (Low/Medium/High). I'm reusing the risk dimension that I've used to color the spheres to illustrate the point.

This is what it looks like if bars are used instead of spheres:

I do admit that it took me a while to get this done, but once you figure out all the components, it is easier than you think. To me at least, it illustrates that we should not be bound to our flat UIs to build business applications and it opens up the door to a range of possibilities (imagine combining it with the speed of HANA), both in business application design or in data analytics.Thinking it through, it allows us to marry up game and business application development in a strange and lovingly weird cross-breed that opens up perspectives on all sides.

Business cases should be found of course, no discussion there, but who wants to be bound by those 😉

As with all experiments: all feedback is very much welcome. I've started a ghost blog on the topic (where I'm providing more details), which can be found here: https://dataanalyticsinvr.ghost.io/

1 Comment
Labels in this area