private static bool TryGetEntityOrigin(VmfEntity entity, out Vector3 result)
 {
     result = default;
     if (entity.TryGetProperty("origin", out VmfVector3 v))
     {
         result = new Vector3(v.X * inchesInMeters, v.Z * inchesInMeters, v.Y * inchesInMeters);
         return(true);
     }
     return(false);
 }
        private static bool TryGetEntityRotation(VmfEntity entity, out Quaternion result)
        {
            result = new Quaternion();
            bool success = false;

            if (entity.TryGetProperty("angles", out VmfVector3 angles))
            {
                result  = Quaternion.Euler(-angles.X, -angles.Y + 90, angles.Z);
                success = true;
            }
            if (entity.TryGetProperty("pitch", out float pitch))
            {
                if (pitch != 0.0f)
                {
                    result.eulerAngles = new Vector3(-pitch, result.eulerAngles.y, result.eulerAngles.z);
                }
                success = true;
            }
            return(success);
        }
        /// <summary>
        /// Imports the entities and attaches them to the specified parent.
        /// </summary>
        /// <param name="parent">The parent to attach entities to.</param>
        /// <param name="world">The world to be imported.</param>
        public static void Import(Transform parent, VmfWorld world)
        {
#if COM_AETERNUMGAMES_CHISEL_DECALS // optional decals package: https://github.com/Henry00IS/Chisel.Decals
            // create a material searcher to associate materials automatically.
            MaterialSearcher materialSearcher         = new MaterialSearcher();
            HashSet <string> materialSearcherWarnings = new HashSet <string>();
#endif
            // iterate through all entities.
            for (int e = 0; e < world.Entities.Count; e++)
            {
#if UNITY_EDITOR
                UnityEditor.EditorUtility.DisplayProgressBar("Chisel: Importing Source Engine Map (3/3)", "Converting Hammer Entities To Unity Objects (" + (e + 1) + " / " + world.Entities.Count + ")...", e / (float)world.Entities.Count);
#endif
                VmfEntity entity = world.Entities[e];

                switch (entity.ClassName)
                {
                // https://developer.valvesoftware.com/wiki/Light
                // light is a point entity available in all Source games. it creates an invisible, static light source that shines in all directions.
                case "light":
                {
                    // create a new light object:
                    GameObject go = new GameObject("Light");
                    go.transform.parent = GetLightingGroupOrCreate(parent);

                    // set the object position:
                    if (TryGetEntityOrigin(entity, out Vector3 origin))
                    {
                        go.transform.position = origin;
                    }

                    // add a light component:
                    Light light = go.AddComponent <Light>();
                    light.type = LightType.Point;
#if UNITY_EDITOR
                    light.lightmapBakeType = LightmapBakeType.Baked;
#endif
                    light.range = 25.0f;

                    // set the light color:
                    if (entity.TryGetProperty("_light", out VmfVector4 color))
                    {
                        light.intensity = color.W * lightBrightnessScalar;
                        light.color     = new Color(color.X / 255.0f, color.Y / 255.0f, color.Z / 255.0f);
                    }

                    break;
                }

                // https://developer.valvesoftware.com/wiki/Light_spot
                // light_spot is a point entity available in all Source games. it is a cone-shaped, invisible light source.
                case "light_spot":
                {
                    // create a new light object:
                    GameObject go = new GameObject("Spot Light");
                    go.transform.parent = GetLightingGroupOrCreate(parent);

                    // set the object position:
                    if (TryGetEntityOrigin(entity, out Vector3 origin))
                    {
                        go.transform.position = origin;
                    }

                    // set the object rotation:
                    if (TryGetEntityRotation(entity, out Quaternion rotation))
                    {
                        go.transform.rotation = rotation;
                    }

                    // add a light component:
                    Light light = go.AddComponent <Light>();
                    light.type = LightType.Spot;
#if UNITY_EDITOR
                    light.lightmapBakeType = LightmapBakeType.Mixed;
#endif
                    light.range = 10.0f;

                    // set the light color:
                    if (entity.TryGetProperty("_light", out VmfVector4 color))
                    {
                        light.intensity = color.W * lightBrightnessScalar;
                        light.color     = new Color(color.X / 255.0f, color.Y / 255.0f, color.Z / 255.0f);
                    }

                    // approximate the light cookie cone shape and the spot angle.
                    if (entity.TryGetProperty("_inner_cone", out int inner_cone) && entity.TryGetProperty("_cone", out int cone))
                    {
                        float lightInnerCone = Mathf.Min(inner_cone * 2, 175);
                        float lightCone      = Mathf.Min(cone * 2, 175);

                        if (lightInnerCone > lightCone)
                        {
                            float t = lightCone;
                            lightInnerCone = lightCone;
                            lightCone      = t;
                        }

                        // set the spot angle:
                        light.spotAngle = lightCone;

                        // generate and set the light cookie:
                        float coneFactor = Mathf.Max(0, lightInnerCone / lightCone);
                        light.cookie = BuildLightCookieTexture(coneFactor);
                    }
                    // backup approach for the spot angle.
                    else if (entity.TryGetProperty("_cone", out int cone2))
                    {
                        // set the spot angle:
                        float lightCone = Mathf.Min(cone2 * 2, 175);
                        light.spotAngle = lightCone;
                    }

                    break;
                }

#if COM_AETERNUMGAMES_CHISEL_DECALS // optional decals package: https://github.com/Henry00IS/Chisel.Decals
                case "infodecal":
                {
                    // create a new decal object:
                    GameObject go = new GameObject("Decal");
                    go.transform.parent = GetDecalsGroupOrCreate(parent);

                    // set the object position:
                    if (TryGetEntityOrigin(entity, out Vector3 origin))
                    {
                        go.transform.position = origin;
                    }

                    // add the decal component:
                    ChiselDecal decal = go.AddComponent <ChiselDecal>();

                    // assign the material:
                    if (entity.TryGetProperty("texture", out string texture))
                    {
                        Material material = FindMaterial(materialSearcher, materialSearcherWarnings, texture);
                        if (material != null)
                        {
                            go.GetComponent <MeshRenderer>().sharedMaterial = material;
                            var mainTexture = material.mainTexture;
                            if (mainTexture != null)
                            {
                                // use the texture size to determine the size of the decal.
                                go.transform.localScale = new Vector3(mainTexture.width * 0.008f, mainTexture.height * 0.008f, 0.1f);
                            }
                        }
                    }

                    // it should be snug against a surface- so we try to find it.
                    RaycastHit raycastHit = default;
                    bool       hit        = false;

                    Vector3 r = Vector3.right * 0.1f;
                    Vector3 f = Vector3.forward * 0.1f;
                    Vector3 u = Vector3.up * 0.1f;

                    // try a ray cast in all world axis to find a hit.
                    if (hit = Physics.Raycast(go.transform.position - r, r, out RaycastHit hitInfo1, 0.2f))
                    {
                        raycastHit = hitInfo1;
                    }
                    if (!hit && (hit = Physics.Raycast(go.transform.position + r, -r, out RaycastHit hitInfo2, 0.2f)))
                    {
                        raycastHit = hitInfo2;
                    }
                    if (!hit && (hit = Physics.Raycast(go.transform.position - f, f, out RaycastHit hitInfo3, 0.2f)))
                    {
                        raycastHit = hitInfo3;
                    }
                    if (!hit && (hit = Physics.Raycast(go.transform.position + f, -f, out RaycastHit hitInfo4, 0.2f)))
                    {
                        raycastHit = hitInfo4;
                    }
                    if (!hit && (hit = Physics.Raycast(go.transform.position - u, u, out RaycastHit hitInfo5, 0.2f)))
                    {
                        raycastHit = hitInfo5;
                    }
                    if (!hit && (hit = Physics.Raycast(go.transform.position + u, -u, out RaycastHit hitInfo6, 0.2f)))
                    {
                        raycastHit = hitInfo6;
                    }

                    // shouldn't not hit unless the level designer actually messed up.
                    if (hit)
                    {
                        // now we have the normal of the surface to "face align" the decal.
                        go.transform.rotation = Quaternion.LookRotation(-raycastHit.normal);
                    }

                    break;
                }
#endif
                }
            }
        }