GameLabGraz / Maroon

An interactive and immersive laboratory for Web, PC and VR.
https://maroon.tugraz.at
31 stars 16 forks source link

Maroon-548 Support to load JSON Experiment Config via URL Fragment #551

Open FlorianGlawogger opened 2 weeks ago

FlorianGlawogger commented 2 weeks ago

Close #548

The idea is that the JSON config is now stored in the URL. The problem is a normal GET request/URL parameters are limited to about 2000 characters, depending on the browser. however, for URL fragments/hash (the part after a # in a url) is not limited (okay, actually it is limited, but on Chrome to 2MB, so for most scenarios enough for us by a factor of 100). See my comment in #548 for more info about this and alternative approaches.

After some trial and error I ended up with this solution, which does not have too many large changes as most new things are handled via Javascript. The most relevant changes:

Example Usage

To use this new feature, you need to assemble an URL, and parse the URL with javascript

URL

You need to somehow assemble an URL via javascript that includes the experiment config, the format looks like this: http://localhost:8000/?LoadScene=EXPERIMENT#config=JSONCONFIG, For example: http://localhost:8000/?LoadScene=Optics#config={%20%22$type%22:%20%22[Maroon](http://localhost:8000/?LoadScene=Optics#config={%20%22$type%22:%20%22Maroon.Physics.Optics.Manager.OpticsParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22presetNameTranslationKey%22:%20%22Microscope%22,%20%22rayThickness%22:%200.08,%20%22cameraSettingBaseView%22:%20{%20%22$type%22:%20%22Maroon.Physics.Optics.Camera.CameraControls%2BCameraSetting,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%20-0.12,%20%22y%22:%202.6,%20%22z%22:%201.0%20},%20%22Rotation%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableQuaternion,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%200.4422887,%20%22y%22:%200.0,%20%22z%22:%200.0,%20%22w%22:%200.896872759%20},%20%22FOV%22:%203.0%20},%20%22cameraSettingTopView%22:%20{%20%22$type%22:%20%22Maroon.Physics.Optics.Camera.CameraControls%2BCameraSetting,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%20-0.12,%20%22y%22:%203.0,%20%22z%22:%202.08%20},%20%22Rotation%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableQuaternion,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%200.707106769,%20%22y%22:%200.0,%20%22z%22:%200.0,%20%22w%22:%200.707106769%20},%20%22FOV%22:%203.0%20},%20%22tableObjectParameters%22:%20{%20%22$type%22:%20%22System.Collections.Generic.List%601[[Maroon.Physics.Optics.TableObject.TableObjectParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null]],%20mscorlib,%20Version=4.0.0.0,%20Culture=neutral,%20PublicKeyToken=b77a5c561934e089%22,%20%22$values%22:%20[%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.LightComponent.PointSourceParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22numberOfRays%22:%2040,%20%22rayDistributionAngle%22:%2022.0,%20%22enableMeshRenderer%22:%20true,%20%22waveLengths%22:%20null,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.78,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.ApertureParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Rin%22:%200.002,%20%22Rout%22:%200.05,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.809,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.LensParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22R1%22:%200.028,%20%22R2%22:%20-0.028,%20%22d1%22:%200.0005,%20%22d2%22:%200.0005,%20%22Rc%22:%200.52678,%20%22A%22:%201.7,%20%22B%22:%200.0,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.81,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.LensParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22R1%22:%200.028,%20%22R2%22:%20-0.028,%20%22d1%22:%200.0005,%20%22d2%22:%200.0005,%20%22Rc%22:%200.52678,%20%22A%22:%201.7,%20%22B%22:%200.0,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.89,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.EyeParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22f%22:%200.024,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.907,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20}%20]%20}%20}).Physics.Optics.Manager.OpticsParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22presetNameTranslationKey%22:%20%22Microscope%22,%20%22rayThickness%22:%200.08,%20%22cameraSettingBaseView%22:%20{%20%22$type%22:%20%22Maroon.Physics.Optics.Camera.CameraControls%2BCameraSetting,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%20-0.12,%20%22y%22:%202.6,%20%22z%22:%201.0%20},%20%22Rotation%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableQuaternion,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%200.4422887,%20%22y%22:%200.0,%20%22z%22:%200.0,%20%22w%22:%200.896872759%20},%20%22FOV%22:%203.0%20},%20%22cameraSettingTopView%22:%20{%20%22$type%22:%20%22Maroon.Physics.Optics.Camera.CameraControls%2BCameraSetting,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%20-0.12,%20%22y%22:%203.0,%20%22z%22:%202.08%20},%20%22Rotation%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableQuaternion,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%200.707106769,%20%22y%22:%200.0,%20%22z%22:%200.0,%20%22w%22:%200.707106769%20},%20%22FOV%22:%203.0%20},%20%22tableObjectParameters%22:%20{%20%22$type%22:%20%22System.Collections.Generic.List%601[[Maroon.Physics.Optics.TableObject.TableObjectParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null]],%20mscorlib,%20Version=4.0.0.0,%20Culture=neutral,%20PublicKeyToken=b77a5c561934e089%22,%20%22$values%22:%20[%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.LightComponent.PointSourceParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22numberOfRays%22:%2040,%20%22rayDistributionAngle%22:%2022.0,%20%22enableMeshRenderer%22:%20true,%20%22waveLengths%22:%20null,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.78,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.ApertureParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22Rin%22:%200.002,%20%22Rout%22:%200.05,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.809,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.LensParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22R1%22:%200.028,%20%22R2%22:%20-0.028,%20%22d1%22:%200.0005,%20%22d2%22:%200.0005,%20%22Rc%22:%200.52678,%20%22A%22:%201.7,%20%22B%22:%200.0,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.81,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.LensParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22R1%22:%200.028,%20%22R2%22:%20-0.028,%20%22d1%22:%200.0005,%20%22d2%22:%200.0005,%20%22Rc%22:%200.52678,%20%22A%22:%201.7,%20%22B%22:%200.0,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.89,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20},%20{%20%22$type%22:%20%22Maroon.Physics.Optics.TableObject.OpticalComponent.EyeParameters,%20Maroon.Experiments.OpticsSimulations,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22f%22:%200.024,%20%22position%22:%20{%20%22$type%22:%20%22Maroon.Utils.SerializableVector3,%20Maroon.ReusableScripts.Utils,%20Version=0.0.0.0,%20Culture=neutral,%20PublicKeyToken=null%22,%20%22x%22:%201.907,%20%22y%22:%200.0,%20%22z%22:%200.577%20},%20%22rotation%22:%20null%20}%20]%20}%20}

Just make sure the URL is escaped properly! E.g. a + is often mistaken for a space from browsers, you need to replace all + occurances with %2B instead.

Javascript

There are multiple ways how this can be done, but here is an example: You need to have a function that sends data to Unity. Since this will be called very early, you might want to create an async function to wait until the unityInstance object is not null anymore.

function sendConfig(config) {
        try {
            // Send configuration to Maroon.
            sendDataToUnity(config);
        } catch (e) {
            alert('Check the syntax of the input. ' + e);
        }
    }

    async function sendDataToUnity(data){
        console.log("Send Data to Unity");

        if (!unityInstance)
        {
            console.log("UnityInstance is null, waiting...");
        }
        await waitForNonNullUnityInstance();

        unityInstance.SendMessage("WebGL Receiver", "GetDataFromJavaScript", data);
    }

    async function waitForNonNullUnityInstance() {
      return new Promise(resolve => {
        const checkInterval = setInterval(() => {
          if (unityInstance !== null) {
            clearInterval(checkInterval);
            resolve(unityInstance); // Resolves once unityInstance is no longer null
          }
        }, 100); // Check every 100ms
      });
    }

Then you need the code which parses the URL fragment

function parseUrlFragment()
      {
        // Access the URL fragment (after the #)
        const fragment = window.location.hash.substr(1);  // Remove the '#' symbol
        const fragmentParams = new URLSearchParams(fragment);
        const config = fragmentParams.get('config');

        if (!config)
        {
            console.log("URL Fragment Config is empty.");
        }
        else
        {
            console.log("URL Fragment Config: ", config);
            sendConfig(config);
        }
      }

I added a call to parseUrlFragment in the UnityProgress function, which is already in the Maroon web template

      function UnityProgress(progress)
      {
          var row_progress    = document.getElementById('row-progress');
          var row_game        = document.getElementById('row-game');
          var progressbar     = document.getElementById('load-progressbar');

          progressbar.style.width = (100 * progress) + "%";

          if (progress == 1)
          {
              row_progress.style.display = "none";
              row_game.style.display = "flex";
              enableFitGame();
          parseUrlFragment();
          }
      }