Improbable Icon

Community Initiative: Friends Of Spatial

community

#1

Hello dear Community members!

Spatial OS is an exciting new platform with great potential. It is also a new platform meaning that we share a lot of knowledge and code snippets here on the forums. In order to improve our community and share the best of the best between eachother I would like to launch the Friends of Spatial initiative.

The premise is simple: I have just registered a Github organisation named Friends Of Spatial where we, as a community, can setup repositories with components, example workers or other shareable independent code. As a start, I am in the process of sharing my Floating Origin component as the first contribution.

At the moment I administrate the github organisation (someone has to do it) but I would like to invite everyone who has a re-usable component to contact me so that I can create a repository and team for you to make something that everyone can use!

What do you people think? Useful?


Community Cookbook?
#2

Yes, I like the idea of a place to group together helpful code for spatial. I have my multiple scene code I can add but since they said it’s coming soon, I’m not sure if it is as useful anymore.


#3

Thanks! This is a wonderful initiative! At the moment I don’t have any code yet, but I hope to be able to contribute later on :slight_smile:

Thanks for starting this!


#4

For those interested; I have open-sourced my Floating Origin script. It is not perfect (see issue listing) but so far it has served me well :slight_smile:


#5

Very good idea, I’ll be keeping this in mind!


#6

All I really have is a few helper scripts that I could comment and throw up (like my reapply trees script, it’s kinda like the shaders one but to bring back trees on terrain), it’s not really complicated, but if anyone wants em I can start a toolbox section for little scripts.


#7

I love the idea of having small re-usable components! Perhaps we can make separate repositories out of them so that people can mix’n’match what they want?


#8

Here are my multi-scene build for unity scripts. https://github.com/judah4/multiscene-build
Added your paths to the scene list json and each worker will get the correct scenes in the same order. I even have simple validation in a context menu that makes sure the scenes exist so you don’t have to build to find out if the file was set properly.


#9

Hey @judah4!

Apparently I missed this post! Sorry for that! I am now adding it to the organisation and making you admin


#10

Hey!

I made a thing! It’s a script that encode Quaternions to use less bits when sent over the network.
It’s in a rough state and there’s plenty of possible optimization but it’s a good start.

Let me know what you think!

For Unity C#

Edit1: fixed some stuff & added quantization


type EncodedQuaternion {
	LargestComponent largest_component = 1;
	bytes component_one = 2;
	bytes component_two = 3;
	bytes component_three = 4;
}

enum LargestComponent {
	X = 0;
	Y = 1;
	Z = 2;
	W = 3;
}

const double squareRootOfTwo = 1.414213562373095;
    
const double quaternionSmallestThreeMaxValue = 1 / squareRootOfTwo;
const double quaternionSmallestThreeMinValue =  -quaternionSmallestThreeMaxValue;
const double quaternionSmallestThreeAbsoluteRange = quaternionSmallestThreeMaxValue - quaternionSmallestThreeMinValue;

const double whateverThisIs = short.MaxValue * quaternionSmallestThreeMaxValue;

const uint ushortPossibleValues = ushort.MaxValue + 1;

public static EncodedQuaternion EncodeQuaternion(Quaternion rotation)
    {
        float largestComponentValue = 0;
        byte largestComponentIndex = 0;
        bool largestComponentSign = true;
        for (byte i = 0; i < 4; i++)
        {
            var value = rotation[i];

            if (Mathf.Abs(value) > Mathf.Abs(largestComponentValue))
            {
                largestComponentValue = value;
                largestComponentIndex = i;
                largestComponentSign = value > 0;
            }
        }

        //ushort[] quantizedComponents = new ushort[3];

        byte[][] encodedComponents = new byte[3][];

        byte shiftedIndex = 0;
        for (byte i = 0; i < 4; i++)
        {
            var value = rotation[i];
            
            if (i != largestComponentIndex)
            {
                if (!largestComponentSign)
                {
                    value = -value;
                }

                ushort quantizedValue  = QuantizeQuaternionComponent(value);
                //quantizedComponents[shiftedIndex] = quantizedValue;
                encodedComponents[shiftedIndex] = System.BitConverter.GetBytes(quantizedValue);
                shiftedIndex++;
            }
        }

        //Debug.Log("Original:(" + rotation[0] + ", " + rotation[1] + ", " + rotation[2] + ", " + rotation[3] + ")");
        //Debug.Log("Quantized:(" + quantizedComponents[0] + ", " + quantizedComponents[1] + ", " + quantizedComponents[2] + ")");

        return new EncodedQuaternion(
            (LargestComponent)largestComponentIndex,
            encodedComponents[0],
            encodedComponents[1],
            encodedComponents[2]);
    }

public static Quaternion DecodeQuaternion(EncodedQuaternion rotation)
    {
        ushort[] decodedComponents = new ushort[3] {
            System.BitConverter.ToUInt16(rotation.componentOne, 0),
            System.BitConverter.ToUInt16(rotation.componentTwo, 0),
            System.BitConverter.ToUInt16(rotation.componentThree, 0) };

        //Debug.Log("Decoded:(" + decodedComponents[0] + ", " + decodedComponents[1] + ", " + decodedComponents[2] + ")");

        double[] unquantizedComponents = new double[3] {
            UnquantizeQuaternionComponent(decodedComponents[0]),
            UnquantizeQuaternionComponent(decodedComponents[1]),
            UnquantizeQuaternionComponent(decodedComponents[2]) };

        //Debug.Log("Unquantized:(" + unquantizedComponents[0] + ", " + unquantizedComponents[1] + ", " + unquantizedComponents[2] + ")");

        Quaternion result = Quaternion.identity;
        byte shiftedIndex = 0;
        for (byte i = 0; i < 4; i++)
        {
            if (i == (byte)rotation.largestComponent)
            {
                var component = System.Math.Sqrt(1 - (unquantizedComponents[0] * unquantizedComponents[0]) - (unquantizedComponents[1] * unquantizedComponents[1]) - (unquantizedComponents[2] * unquantizedComponents[2]));

                result[i] = (float)component;
            }
            else
            {
                result[i] = (float)unquantizedComponents[shiftedIndex];
                shiftedIndex++;
            }
        }

        //Debug.Log("Result:" + result.ToString());

        return result;
    }
    
    static ushort QuantizeQuaternionComponent(double value)
    {
        var result = (value - quaternionSmallestThreeMinValue) / quaternionSmallestThreeAbsoluteRange;// scale value from range -0.707107 @ +0.707107 to range 0 @ +1

        result *= ushortPossibleValues;// scale range to 0 @ +65535

        //It never actually happen
        /*if (result < 0) 
        {
            Debug.LogError("Result cannot be smaller than 0");
            return 0;
        }
        else if (result > ushort.MaxValue)
        {
            Debug.LogError("Result cannot be bigger than 65535");
            return ushort.MaxValue;
        }*/

        return (ushort)result;
    }

    static double UnquantizeQuaternionComponent(ushort x)
    {
        double result = (double)x + short.MinValue;// center result to range -32768 @ +32767

        result /= short.MaxValue;// scale result to range -1 @ +1

        result *= quaternionSmallestThreeMaxValue;// scale result to range -0.707107 @ +0.707107

        return result;
    }

#11

Nice!

This is a great start, and will certainly save a lot over the basic 4 doubles approach!
Like you said there are certainly more optimisations you could make, but I’ll make a couple of spatial-specific comments.

Firstly, you would save a small amount by using one ‘bytes’ value rather than three, given that in any single update it is unlikely that one of the values would change independently of the others. This is only a tiny saving though.

Second, it would be nice to be able to choose the quantisation level somehow. You could even store this in the component itself without too much overhead and have it configurable per entity. You could store a small int representing the number of bits of precisions, and given that an update only contains the fields that change, there would only be overhead when the entity is checked out on the worker.

Thanks for sharing this! I think this kind of thing will be very useful.

Chris


#12

Hey!

About using one bytes, would it be better to actually use 3 uint32 since small value will use less bytes, because varints encoding or is the overhead of changing 3 fields negate the gain?

edit: could you move this and your previous answer to my network optimization thread I don’t want to clutter the nice thread here


#13

Oh How can I join it?
:eyeglasses:


#14

You’re in! :smiley:

If you have code to share; let me know under what name and what your github name is and I’ll create a repo and give you all rights on it :slight_smile:


#15

Join me then


#16

If you have code to share; let me know under what name and what your github name is and I’ll create a repo and give you all rights on it


#17

GitHub Name: TrojanFighter
the same as this one. THX


#18

@draconigra -

I was sent here from my Community Cookbook thread in Feedback. I’d love to contribute my Character Controller to the org. Can you add a repo MMORPGController and make me a guy so I can port my code and a tutorial over?

Github Handle is chrisisbeef


#19

Hi @TrojanFighter and @krnlpanick!

I have created repos for both and made you admin on your respective repo. This means you can push, pull, add collaborators and more :slight_smile:


#20

Good news everyone!

My Firebase Authentication For Unity & JWT Verification is up.

Comments and pull requests welcome!