void DumpBiomeTree(BiomeSwitchNode node, int depth = 0)
        {
            Debug.Log("node at depth " + depth + ": " + node);

            foreach (var child in node.GetChilds())
            {
                DumpBiomeTree(child, depth + 1);
            }
        }
        public void BuildTree(PWNode node)
        {
            biomeIdCount = 0;
            Stopwatch st = new Stopwatch();

            st.Start();
            root = new BiomeSwitchNode();
            biomeCoverage.Clear();
            foreach (var switchMode in Enum.GetValues(typeof(BiomeSwitchMode)))
            {
                biomeCoverage[(BiomeSwitchMode)switchMode] = 0;
            }
            BuildTreeInternal(node, root, 0);
            st.Stop();

            Debug.Log("built tree time: " + st.ElapsedMilliseconds + "ms");

            // DumpBuiltTree();

            isBuilt = true;
        }
        void DumpBuiltTree()
        {
            BiomeSwitchNode current = root;

            Debug.Log("built tree:");
            DumpBiomeTree(root);
            string childs = "";

            foreach (var child in current.GetChilds())
            {
                childs += child + "(" + child.biome + ") | ";
            }
            Debug.Log("swicth line1: " + childs);

            childs = "";
            foreach (var child in current.GetChilds())
            {
                foreach (var childOfChild in child.GetChilds())
                {
                    childs += childOfChild + "(" + childOfChild.biome + ") | ";
                }
            }
            Debug.Log("swicth line2: " + childs);
        }
        public void FillBiomeMap(int biomeBlendCount, BiomeData biomeData)
        {
            bool is3DBiomes  = false;
            bool is3DTerrain = biomeData.terrain3D != null;

            Biome[] nearestBiomes = new Biome[biomeBlendCount];

            //TODO: biome blend count > 1 management

            //TODO: biomeData.datas3D null check
            if (biomeData.air3D != null || biomeData.wind3D != null || biomeData.wetness3D != null || biomeData.temperature3D != null)
            {
                is3DBiomes = true;
            }

            if (biomeData.terrainRef == null)
            {
                return;
            }

            int   terrainSize = (is3DTerrain) ? biomeData.terrain3D.size : biomeData.terrain.size;
            float terrainStep = (is3DTerrain) ? biomeData.terrain3D.step : biomeData.terrain.step;

            if (is3DBiomes)
            {
                biomeData.biomeIds3D = new BiomeMap3D(terrainSize, terrainStep);
            }
            else
            {
                biomeData.biomeIds = new BiomeMap2D(terrainSize, terrainStep);
            }

            if (is3DBiomes)
            {
                //TODO
            }
            else
            {
                for (int x = 0; x < terrainSize; x++)
                {
                    for (int y = 0; y < terrainSize; y++)
                    {
                        bool  water = (biomeData.isWaterless) ? false : biomeData.waterHeight[x, y] > 0;
                        float temp  = (biomeData.temperature != null) ? biomeData.temperature[x, y] : 0;
                        float wet   = (biomeData.wetness != null) ? biomeData.wetness[x, y] : 0;
                        // Debug.Log("map [" + x + "/" + y + "] = " + biomeData.terrain[x, y]);
                        //TODO: 3D terrain management
                        float           height  = (is3DTerrain) ? 0 : biomeData.terrain[x, y];
                        BiomeSwitchNode current = root;
                        while (true)
                        {
nextChild:
                            if (current.biome != null)
                            {
                                break;
                            }
                            int childCount = current.GetChildCount();
                            for (int i = 0; i < childCount; i++)
                            {
                                var child = current.GetChildAt(i);
                                switch (child.biomeSwitchMode)
                                {
                                case BiomeSwitchMode.Water:
                                    if (child.value == water)
                                    {
                                        current = child; goto nextChild;
                                    }
                                    break;

                                case BiomeSwitchMode.Height:
                                    if (height > child.min && height <= child.max)
                                    {
                                        current = child; goto nextChild;
                                    }
                                    break;

                                case BiomeSwitchMode.Temperature:
                                    if (temp > child.min && temp <= child.max)
                                    {
                                        current = child; goto nextChild;
                                    }
                                    break;

                                case BiomeSwitchMode.Wetness:
                                    if (wet > child.min && wet <= child.max)
                                    {
                                        current = child; goto nextChild;
                                    }
                                    break;
                                }
                            }
                            //if flow reach this part, values are missing in the biome graph so biome can't be chosen.
                            break;
                        }
                        //TODO: blending with second biome
                        if (current.biome != null)
                        {
                            biomeData.biomeIds.SetFirstBiomeId(x, y, current.biome.id);
                        }
                        else
                        {
                            //FIXME!!!!
                            biomeData.biomeIds.SetFirstBiomeId(x, y, -1);
                            // PWUtils.LogWarningMax("Can't choose biome with water:" + water + ", temp: " + temp + ", wet: " + wet + ", height: " + height, 200);
                            PWUtils.LogWarningMax("Can't choose biome with water:" + water + ", temp: " + temp + ", wet: " + wet + ", height: " + height, 300);
                            continue;
                        }
                    }
                }
            }
        }
        void BuildTreeInternal(PWNode node, BiomeSwitchNode currentNode, int depth, BiomeSwitchNode parent = null)
        {
            if (node == null)
            {
                return;
            }

            //TODO: anchor to multiple PWNodeBiomeSwitch management
            if (node.GetType() == typeof(PWNodeBiomeSwitch))
            {
                PWNodeBiomeSwitch bSwitch      = node as PWNodeBiomeSwitch;
                int           outputLinksCount = bSwitch.GetLinks().Count;
                int           childIndex       = 0;
                List <PWNode> outputNodeList   = new List <PWNode>();

                Debug.Log("encountered switch: " + bSwitch.switchMode);
                currentNode.SetChildCount(outputLinksCount);
                switch (bSwitch.switchMode)
                {
                case BiomeSwitchMode.Water:
                    int?terrestrialAnchorId = node.GetAnchorId(PWAnchorType.Output, 0);
                    int?aquaticAnchorId     = node.GetAnchorId(PWAnchorType.Output, 1);

                    if (terrestrialAnchorId != null)
                    {
                        var nodes = node.GetNodesAttachedToAnchor(terrestrialAnchorId.Value);
                        outputNodeList.AddRange(nodes);
                        // Debug.Log("terrestrialNodes: " + nodes[0].nodeId);
                        for (int i = 0; i < nodes.Count; i++)
                        {
                            currentNode.GetChildAt(childIndex++).SetSwitchValue(false, bSwitch.switchMode, "terrestrial", Color.black);
                        }
                        biomeCoverage[BiomeSwitchMode.Water] += 0.5f;
                    }
                    //get all nodes on the first anchor:
                    if (aquaticAnchorId != null)
                    {
                        var nodes = node.GetNodesAttachedToAnchor(aquaticAnchorId.Value);
                        // Debug.Log("aquaticNodes: " + nodes[0].nodeId);
                        outputNodeList.AddRange(nodes);
                        for (int i = 0; i < nodes.Count; i++)
                        {
                            currentNode.GetChildAt(childIndex++).SetSwitchValue(true, bSwitch.switchMode, "aquatic", Color.blue);
                        }
                        biomeCoverage[BiomeSwitchMode.Water] += 0.5f;
                    }

                    break;

                default:
                    // Debug.Log("swicth data count for node " + node.nodeId + ": " + bSwitch.switchDatas.Count);

                    for (int anchorIndex = 0; anchorIndex < bSwitch.switchDatas.Count; anchorIndex++)
                    {
                        int?anchorId = node.GetAnchorId(PWAnchorType.Output, anchorIndex);
                        var sData    = bSwitch.switchDatas[anchorIndex];

                        if (anchorId == null)
                        {
                            continue;
                        }

                        var attachedNodesToAnchor = node.GetNodesAttachedToAnchor(anchorId.Value);
                        outputNodeList.AddRange(attachedNodesToAnchor);

                        // if (attachedNodesToAnchor.Count == 0)
                        // Debug.LogWarning("nothing attached to the biome switch output " + anchorIndex);

                        foreach (var attachedNode in attachedNodesToAnchor)
                        {
                            var child = currentNode.GetChildAt(childIndex++);

                            child.SetSwitchValue(sData.min, sData.max, bSwitch.switchMode, sData.name, sData.color);
                        }

                        biomeCoverage[bSwitch.switchMode] += (sData.max - sData.min) / (sData.absoluteMax - sData.absoluteMin);
                    }
                    break;
                }
                childIndex = 0;
                foreach (var outNode in outputNodeList)
                {
                    if (bSwitch.switchMode == BiomeSwitchMode.Water)
                    {
                        Debug.Log("water switch mode output type: " + currentNode.GetChildAt(childIndex) + " [" + childIndex + "] -> " + outNode);
                    }
                    BuildTreeInternal(outNode, currentNode.GetChildAt(childIndex, true), depth + 1, currentNode);
                    Type outNodeType = outNode.GetType();
                    if (outNodeType == typeof(PWNodeBiomeSwitch) || outNodeType == typeof(PWNodeBiomeBinder))
                    {
                        childIndex++;
                    }
                }
            }
            else if (node.GetType() == typeof(PWNodeBiomeBinder))
            {
                PWNodeBiomeBinder binder = node as PWNodeBiomeBinder;

                string biomeName = currentNode.biomeName;

                if (String.IsNullOrEmpty(biomeName))
                {
                    Debug.LogWarning("Biome name null or empty for biomeBinder: " + binder);
                    return;
                }

                //Biome binder detected, assign the biome to the current Node:
                currentNode.biome = binder.outputBiome;

                // Debug.Log("current node: " + currentNode + ", preview color: " + currentNode.previewColor);

                //set the color of the biome in the binder
                binder.outputBiome.previewColor = currentNode.previewColor;

                Debug.Log("set biome " + currentNode.biome + " in node " + currentNode);

                //set the biome ID and name:
                currentNode.biome.name = biomeName;
                currentNode.biome.id   = biomeIdCount++;

                //store the biome in dictionaries for fast access
                biomePerId[currentNode.biome.id] = currentNode.biome;
                biomePerName[biomeName]          = currentNode.biome;
            }
            else
            {
                foreach (var outNode in node.GetOutputNodes())
                {
                    BuildTreeInternal(outNode, currentNode, depth++, parent);
                }
            }
            return;
        }