private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.layer == 8) // collider layer
        {
            Debug.Log("Hit Raycast Layer!");

            var contacts = collision.contacts;

            Vector3 pos = contacts[0].point;
            Vector3 nrm = contacts[0].normal;

            // we need to fire a ray from contact point into mesh to allow
            // for material reaction
            Ray r = new Ray(pos + (nrm * 0.01f), -nrm);

            APARaycastHit  hit;
            MaterialStruct mat;

            if (MaterialRayCastSystem.RayVsSceneMaterial(r, out hit, out mat))
            {
                // Depending on material it should maybe shatter?
                if (mat.m_breakOnImpact == 1)
                {
                    // Spawn sounds yey
                    {
                        GameObject soundSpawn = new GameObject();
                        soundSpawn.transform.position = transform.position;
                        var ac = soundSpawn.AddComponent <AudioSource>();

                        int randIndex = Random.Range(0, m_brokenSoundFX.Count);
                        ac.clip = m_brokenSoundFX[randIndex];
                        ac.Play();
                    }

                    // Trigger Game Reaction
                    Vector3 hitWsPosition = hit.point;
                    Vector3 hitWsNormal   = hit.transform.TransformVector(hit.normal);

                    // Shatter
                    var particle = Instantiate(m_explosionParticle) as GameObject;
                    particle.transform.SetPositionAndRotation(hitWsPosition, Quaternion.LookRotation(hitWsNormal, Vector3.up));

                    m_gameController.GameReaction(hitWsPosition, hitWsNormal, mat);

                    Destroy(gameObject);
                }
            }
        }
    }
    private void MeshPreProcessInteractionRoutine()
    {
        Ray            r = m_meshRenderCamera.ScreenPointToRay(Input.mousePosition);
        APARaycastHit  hit;
        MaterialStruct mat;

        if (MaterialRayCastSystem.RayVsSceneMaterial(r, out hit, out mat))
        {
            Vector3 hitWsPosition = hit.point;
            Vector3 hitWsNormal   = hit.transform.TransformVector(hit.normal);

            GameReaction(hitWsPosition, hitWsNormal, mat);
        }
        else
        {
            m_uiMaterialColourOutput.color = m_errorColour;
            m_uiMaterialTextOutput.text    = "No Data / Ray Missed ";
        }
    }
    // Has to be OnPostRender so we can grab render targets safely
    private void OnPostRender()
    {
        if (m_lastWidth != Screen.width || m_lastHeight != Screen.height)
        {
            OnResize();
        }

        if (m_rayCastTriggered)
        {
            m_rayCastTriggered = false;
            var zDepth = m_textureOverlay.depthXYZZed;

            // Store current RT
            RenderTexture currentRT = RenderTexture.active;

            // Create new render target to copy
            {
                RenderTexture.active = m_zedRT;

                Graphics.Blit(zDepth, m_zedRT);

                Texture2D zedReadable = new Texture2D(zDepth.width, zDepth.height, zDepth.format, false);
                zedReadable.ReadPixels(new Rect(0, 0, m_zedRT.width, m_zedRT.height), 0, 0);
                zedReadable.Apply();

                Vector2 relMousePos = new Vector2();

                relMousePos.x = m_mousePositionWhenTriggered.x / Screen.width;
                relMousePos.y = m_mousePositionWhenTriggered.y / Screen.height;

                Vector2 dPixUv = relMousePos;

                dPixUv.y = 1.0f - dPixUv.y;

                dPixUv.x *= zedReadable.width;
                dPixUv.y *= zedReadable.height;

                // this only works for starting camera position and rotation
                var            depthPixelData = zedReadable.GetPixel((int)dPixUv.x, (int)dPixUv.y);
                var            matPixelData   = new Color(0, 0, 0, 0);
                MaterialStruct matFound       = null;

                if (m_materialColourTexture != null)
                {
                    Vector2 mPixUv = relMousePos;
                    mPixUv.y = 1.0f - mPixUv.y;

                    mPixUv.x *= m_materialColourTexture.width;
                    mPixUv.y *= m_materialColourTexture.height;

                    matPixelData = m_materialColourTexture.GetPixel((int)mPixUv.x, (int)mPixUv.y);

                    matFound = MaterialRayCastSystem.FindMaterialStructFromColour(matPixelData);
                }

                // Need to guard against uninit areas of pixels
                // This can happen if camera hasn't been able to do depth properly in area

                if (
                    float.IsNaN(depthPixelData.r) ||
                    float.IsInfinity(depthPixelData.r) ||
                    float.IsNaN(depthPixelData.g) ||
                    float.IsInfinity(depthPixelData.g) ||
                    float.IsNaN(depthPixelData.b) ||
                    float.IsInfinity(depthPixelData.b)
                    )
                {
                    return;
                }

                // If changed process of getting this position, then remember to udpate for loop at bottom
                Vector3 cVsPos = new Vector3(depthPixelData.r, depthPixelData.g, -depthPixelData.b);

                // get back into object space
                Matrix4x4 inverseView = m_zedCamera.worldToCameraMatrix.inverse;

                Vector3 cWsPos = inverseView * new Vector4(cVsPos.x, cVsPos.y, cVsPos.z, 1.0f);

                // Now we need to find the normal....
                {
                    // https://stackoverflow.com/questions/37627254/how-to-reconstruct-normal-from-depth-without-artifacts-on-edge
                    //     [ ]
                    // [ ] [x] [ ]
                    //     [ ]

                    // 4 samples around edge
                    // find the closest sample top and bottom, left and right relative to center sample
                    // then normalise cross product on it? (x dir, y dir)

                    Vector2[] offsets = new Vector2[4]
                    {
                        new Vector2(0, 15),
                        new Vector2(0, -15),
                        new Vector2(15, 0),
                        new Vector2(-15, 0)
                    };

                    Vector3[] wsPositions  = new Vector3[4];
                    float[]   distToCenter = new float[4];

                    for (int i = 0; i < 4; i++)
                    {
                        Vector2 uvOffset = dPixUv + offsets[i];
                        Color   sample   = zedReadable.GetPixel((int)uvOffset.x, (int)uvOffset.y);
                        Vector4 vsPos    = new Vector4(sample.r, sample.g, -sample.b, 1.0f);

                        // Calculate WS POS
                        Vector3 sampleWsPos = inverseView * vsPos;

                        distToCenter[i] = Vector3.Distance(cWsPos, sampleWsPos);

                        wsPositions[i] = sampleWsPos;
                    }

                    int verticalSampleIndex   = 0;
                    int horizontalSampleIndex = 2;

                    // Find closest vertical sample
                    if (distToCenter[0] > distToCenter[1])
                    {
                        verticalSampleIndex = 1;
                    }

                    // Find closest horizontal sample
                    if (distToCenter[2] > distToCenter[3])
                    {
                        horizontalSampleIndex = 3;
                    }

                    Vector3 normalGen = Vector3.Cross(wsPositions[verticalSampleIndex] - cWsPos, wsPositions[horizontalSampleIndex] - cWsPos);
                    normalGen.Normalize();

                    // Update Game Reaction
                    m_gameController.GameReaction(cWsPos, normalGen, matFound);
                }
            }

            // Restore original active render texture
            RenderTexture.active = currentRT;
        }
    }