Skip to main content

Command Palette

Search for a command to run...

Weekly Devlog 5: Tile Rotation and Size

Updated
•4 min read•View as Markdown
Weekly Devlog 5: Tile Rotation and Size
A
Game dev student. Unity Engine <3

https://www.youtube.com/watch?v=ckph0_erF5I

A lot of this week was spent refactoring code and optimizing performance, but I also added basic Tile Rotation and Size. For this I added a custom TileRotation struct, that represents the rotation as two axis:

using Shared.Utils;
using Unity.Mathematics;
using UnityEngine;

namespace Shared.Data.Groups
{
    public readonly struct TileRotation
    {
        public readonly NormalAxis Forward;
        public readonly NormalAxis Up;

        public NormalAxis Right => Cross(Up, Forward);
        
        public quaternion ToQuaternion() =>
            quaternion.LookRotation(Forward.ToFloat3(), Up.ToFloat3());

        public static readonly TileRotation Identity =
            new (NormalAxis.PositiveZ, NormalAxis.PositiveY);
        
        public TileRotation(NormalAxis forward)
        {
            Forward = forward;
            Up = MathUtils.GetDefaultSecondaryAxis(forward);
        }
        
        public TileRotation(NormalAxis forward, NormalAxis up)
        {
            UnityEngine.Debug.Assert(Vector3.Dot(forward.ToVector3(), up.ToVector3()) < Mathf.Epsilon,
                "Forward and up must be perpendicular.");
            
            Forward = forward;
            Up = up;
        }

        private NormalAxis Cross(NormalAxis axisA, NormalAxis axisB)
        {
            var a = axisA.ToInt3();
            var b = axisB.ToInt3();

            var res = new int3(a.y * b.z - a.z * b.y, 
                a.z * b.x - a.x * b.z, 
                a.x * b.y - a.y * b.x);

            return res.ToAxis();
        }

        public int3 Rotate(int3 v)
        {
            var upVec = Up.ToInt3();
            var forwardVec = Forward.ToInt3();
            var rightVec = (int3)math.cross(upVec, forwardVec);
            return rightVec * v.x + upVec * v.y + forwardVec * v.z;
        }
        
        public float3 Rotate(float3 v)
        {
            var upVec = Up.ToFloat3();
            var forwardVec = Forward.ToFloat3();
            var rightVec = math.cross(upVec, forwardVec);
            return rightVec * v.x + upVec * v.y + forwardVec * v.z;
        }
    }
}

Tiles will be restricted to these 24 possible rotations (6 forward direction * 4 up directions). The tile size is just saved as a simple int3. Making the mesh generation use Rotation and Size was easier than expected:

        public float3 GetCenter()
        {
            //From Grid (Lower left) to Lower left cell center
            var gridStartLocal = GridUtils.GridToLocal(GridStart);
            //Remaining extends to center (remove 1, because we already moved 0.5)
            var halfExtends = (float3)(GridSize - new int3(1, 1, 1)) * 0.5f;
            return gridStartLocal + TileRotation.Rotate(halfExtends);
        }


        public float3 TransformVertex(float3 vertex)
        {
            var rotation = TileRotation.ToQuaternion();
            vertex = math.mul(rotation, vertex);
            vertex += GetCenter();
            return vertex;
        }

The size information gets read from the tile json:

{
  "TileId":"763f57a1-6101-4e4d-95c6-350a901fe3b5",
  "Name":"Box 2x2x2",
  "Category":"testing",
  "MeshReference":"Assets/Models/Tiles/Tiles1.fbx[Box_2x2x2]",
  "Material":"Concrete",
  "GridSize":
  {
    "x": 2,
    "y": 2,
    "z": 2
  }
}

Inertia Tensor

I already implemented Center of Mass and Total Mass calculations, but forgot about the inertia tesnor. I implemented a job that calculates it, which turned out to be a lot harder than I thought. Right now it only does it for unit size Boxes, I still need to add size and shape support.

using Shared.Data.Groups;
using Shared.Data.Tiles;
using Tiles.Data;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;

namespace Groups.PhysicsGeneration
{
    /// <summary>
    ///  Calculates COM, Mass and Inertia Tensor
    /// </summary>
    [BurstCompile]
    public struct PhysicsDataJob : IJob
    {
        public GroupData GroupData;
        public NativeList<TileData> Tiles;

        public NativeReference<PhysicsData> Output;
        
        public void Execute()
        {
            float mass = 0f;
            float3 centerOfMass = float3.zero;

            float3 inertiaDiagonal = float3.zero;

            //1. Calculate COM, Total Mass
            for (var i = 0; i < GroupData.TileList.Length; i++)
            {
                var groupTileData = GroupData.TileList[i];

                var tileCenter = groupTileData.GetCenter();
                var tile = Tiles[groupTileData.TileIndex];
                mass += tile.Mass;
                centerOfMass += tileCenter * tile.Mass;

                //No rotation
                //No size support for now
                var size = new float3(1f);

                var inertiaX = tile.Mass * (Sqr(size.y) + Sqr(size.z)) / 12f;
                var inertiaY = tile.Mass * (Sqr(size.x) + Sqr(size.z)) / 12f;
                var inertiaZ = tile.Mass * (Sqr(size.x) + Sqr(size.y)) / 12f;

                inertiaDiagonal += new float3(inertiaX, inertiaY, inertiaZ);
            }
            
            centerOfMass /= mass;

            float inertiaXY = 0f;
            float inertiaXZ = 0f;
            float inertiaYZ = 0f;
            
            //2. Add more stuff to the inertia tensor
            for (var i = 0; i < GroupData.TileList.Length; i++)
            {
                var groupTileData = GroupData.TileList[i];
                
                var tile = Tiles[groupTileData.TileIndex];
                var tileCenter = groupTileData.GetCenter();
                var r = tileCenter - centerOfMass;
                
                var inertiaX = tile.Mass * (Sqr(r.y) + Sqr(r.z));
                var inertiaY = tile.Mass * (Sqr(r.x) + Sqr(r.z));
                var inertiaZ = tile.Mass * (Sqr(r.x) + Sqr(r.y));
                    
                inertiaDiagonal += new float3(inertiaX, inertiaY, inertiaZ);

                inertiaXY += tile.Mass * r.x * r.y;
                inertiaXZ += tile.Mass * r.x * r.z;
                inertiaYZ += tile.Mass * r.y * r.z;
            }

            inertiaXY *= -1;
            inertiaXZ *= -1;
            inertiaYZ *= -1;

            float3x3 inertiaTensor = new float3x3(
                inertiaDiagonal.x, inertiaXY, inertiaXZ,
                inertiaXY, inertiaDiagonal.y, inertiaYZ,
                inertiaXZ, inertiaYZ, inertiaDiagonal.z);

            EigenSolver.DiagonalizeSymmetricApproximation(inertiaTensor, out var eigenVectors, out var eigenValues);
            
            var det = math.determinant(eigenVectors);
            if (det < 0f)
                eigenVectors.c0 = -eigenVectors.c0;

            var inertiaRotation = new quaternion(eigenVectors);

            Output.Value = new PhysicsData()
            {
                CenterOfMass = centerOfMass,
                TotalMass = mass,
                InertiaTensor = eigenValues,
                InertiaTensorRotation = inertiaRotation
            };
        }
        
        private static float Sqr(float x) => x * x;
    }
}

Luckily I did not have to write the EigenSolver myself, but could use the one that Unity.Physics implements: https://github.com/needle-mirror/com.unity.physics/blob/master/Unity.Physics/Base/Math/Math.cs#L185