# Weekly Devlog 3: Grid Based Builder

%[https://www.youtube.com/watch?v=xcTAsyx5JxA] 

I started implementing the Lego style builder, the player can drag tiles together in order to connect them. The implementation uses the Unity Job system for high performance mesh generation and other operations, as well as GPU instanced highlight spheres.

## Selection spheres

![](https://cdn.hashnode.com/uploads/covers/689f8c80e2f0031b7d8d828e/287db6c1-656d-46a9-b1cc-d1c65218d2f0.png align="center")

When the player hovers a group, he gets a preview of the connection points. These are drawn using GPU instancing in a custom renderpass. The challenge was that they are lit, I wanted them to be influenced by darkness and so on. The easier way would have been to just use gameobjects with object pooling, but I thought I might need to draw a lot of these spheres and implemented instancing instead.

I spend hours trying to find out why my sphere were just black, before realizing that I had to set some stuff on the material in order for ambient lightning to work:

```csharp
private static readonly int _unitySpecCube0 = Shader.PropertyToID("unity_SpecCube0");
private static readonly int _unitySpecCube0HDR = Shader.PropertyToID("unity_SpecCube0_HDR");
private static readonly int _unitySpecCube0ProbePosition = Shader.PropertyToID("unity_SpecCube0_ProbePosition");
        

public MaterialPropertyBlock Block { get; }
private readonly Vector3[] _probePositions;
private readonly SphericalHarmonicsL2[] _lightProbes;
private readonly Vector4[] _probeOcclusion;

private void UpdateProbeData()
{
    LightProbes.CalculateInterpolatedLightAndOcclusionProbes(
        _probePositions,
        _lightProbes,
        _probeOcclusion);

    Block.CopySHCoefficientArraysFrom(_lightProbes);
    Block.CopyProbeOcclusionArrayFrom(_probeOcclusion);

    if (ReflectionProbe.defaultTexture != null)
    {
        Block.SetTexture(
            _unitySpecCube0,
            ReflectionProbe.defaultTexture);

        Block.SetVector(
            _unitySpecCube0HDR,
            ReflectionProbe.defaultTextureHDRDecodeValues);

        Block.SetVector(
            _unitySpecCube0ProbePosition,
            Vector4.zero);
    }
}
```

In order for the main light to work, I also had to set a Rendering Layer:

```csharp

private static readonly int _unityRenderingLayerId = Shader.PropertyToID("unity_RenderingLayer");

        private static void ExecutePass(PointPass data, RasterGraphContext ctx)
        {
            data.Block.SetBuffer(_pointsId, data.Points);
            
            data.Block.SetVector(
                _unityRenderingLayerId,
                new Vector4(math.asfloat(1u), 0f, 0f, 0f));
```

This took very long to figure out.

## Mesh Generation

The job to generate the mesh takes in the tile meshes (right now there is only 1 tile type) and outputs the grouped mesh (combines tiles are called a group).

```csharp
/// <summary>
/// Combine tile meshes into one
/// </summary>
[BurstCompile]
public unsafe struct GroupMeshJob : IJob
{
    [ReadOnly] 
    public Mesh.MeshDataArray TileMeshes;
    public Mesh.MeshData OutputMesh;

    [WriteOnly]
    public NativeReference<float3x2> Bounds;
    [ReadOnly]
    public GroupData GroupData;
```

The challenge here was that the vertex data in the tile mesh contains different information in different formats, as I created the mesh in blender. I had to work with pointers in order to be able to access the data:

```csharp
            var stride = tileMesh.GetVertexBufferStride(0);
            var positionOffset = tileMesh.GetVertexAttributeOffset(VertexAttribute.Position);
            var normalOffset = tileMesh.GetVertexAttributeOffset(VertexAttribute.Normal);
            var colorOffset = tileMesh.GetVertexAttributeOffset(VertexAttribute.Color);
            
            NativeArray<byte> tileMeshVertexData = tileMesh.GetVertexData<byte>(stream: 0);
            var tileMeshVertexDataPtr = (byte*)tileMeshVertexData.GetUnsafeReadOnlyPtr();
            var offset = new float3(0.5f, 0.5f, 0.5f);
            
            for (int j = 0; j < tileMeshVertexData.Length; j += stride)
            {
                //1. Read data
                var positionIndex = j + positionOffset;
                var normalIndex = j + normalOffset;
                var colorIndex = j + colorOffset;

                var position = *(float3*)(tileMeshVertexDataPtr + positionIndex);
                var normal = *(float3*)(tileMeshVertexDataPtr + normalIndex);
                var color = *(Color32*)(tileMeshVertexDataPtr + colorIndex);
                
                //2. Process data
                //TODO: Rotation, relative to center
                position += groupTile.Key + offset;
                
                min = math.min(min, position);
                max = math.max(max, position);
                
                //3. Write data
                var outPositionIndex = vertexByteIndex + outPositionOffset;
                var outNormalIndex = vertexByteIndex + outNormalOffset;
                var outColorIndex = vertexByteIndex + outColorOffset;

                var outPosition = (float3*)(vertexDataPtr + outPositionIndex);
                var outNormal = (float3*)(vertexDataPtr + outNormalIndex);
                var outColor = (Color32*)(vertexDataPtr + outColorIndex);

                outPosition[0] = position;
                outNormal[0] = normal;
                outColor[0] = color;
                
                vertexByteIndex += outStride;
                vertexIndex++;
            }
```

![](https://cdn.hashnode.com/uploads/covers/689f8c80e2f0031b7d8d828e/c60837e3-00bb-4c71-8a07-269b3a94d20f.png align="center")
