public bool SetLight(int2 worldTilePosition, int value, LightType type)
    {
        if (!TileUtil.BoundaryCheck(worldTilePosition, mapSize))
        {
            return(false);
        }

        int index = TileUtil.To1DIndex(worldTilePosition, mapSize);

        if (lights[index].GetLight(type) == value)
        {
            return(false);
        }

        int2 chunkPosition = TileUtil.WorldTileToChunk(worldTilePosition, chunkSize);

        if (chunks.TryGetValue(chunkPosition, out TileChunk chunk))
        {
            chunk.SetLightDirty();
        }
        else
        {
            TileChunk newChunk = GenerateChunk(chunkPosition);
            newChunk.SetLightDirty();
        }

        lights[index].SetLight(value, type);

        return(true);
    }
    void OnDrawGizmos()
    {
        if (tiles == null)
        {
            return;
        }

        Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);

        int2 worldtilePosition = TileUtil.WorldToWorldtile(mousePosition);

        if (!TileUtil.BoundaryCheck(worldtilePosition, mapSize))
        {
            return;
        }

        int tileIndex = TileUtil.To1DIndex(worldtilePosition, mapSize);
        int tile      = tiles[tileIndex];

        if (tileInformations[tile].isSolid)
        {
            return;
        }

        Handles.Label(mousePosition, waterDensities[tileIndex].ToString());
    }
    public int GetTile(int2 worldTilePosition)
    {
        if (!TileUtil.BoundaryCheck(worldTilePosition, mapSize))
        {
            return(-1);
        }

        return(tiles[TileUtil.To1DIndex(worldTilePosition, mapSize)]);
    }
    public int GetLight(int2 worldTilePosition, LightType type)
    {
        if (!TileUtil.BoundaryCheck(worldTilePosition, mapSize))
        {
            return(0);
        }

        return(lights[TileUtil.To1DIndex(worldTilePosition, mapSize)].GetLight(type));
    }
    void Update()
    {
        currentFlowSpeed = Mathf.Clamp01(flowSpeed * Time.deltaTime);

        if (fluidUpdator == null)
        {
            fluidUpdator = StartCoroutine(nameof(UpdateFluid));
        }
        SunLightPropagation();
        TorchLightPropagation(ref torchRedLightPropagationQueue, ref torchRedLightRemovalQueue, LightType.R);
        TorchLightPropagation(ref torchGreenLightPropagationQueue, ref torchGreenLightRemovalQueue, LightType.G);
        TorchLightPropagation(ref torchBlueLightPropagationQueue, ref torchBlueLightRemovalQueue, LightType.B);
        UpdateChunks();

        Vector3 mousePosition     = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        int2    worldTilePosition = TileUtil.WorldToWorldtile(mousePosition);

        if (!TileUtil.BoundaryCheck(worldTilePosition, mapSize))
        {
            return;
        }

        if (Input.GetMouseButton(0))
        {
            SetTile(worldTilePosition, tileInformations[1].id);
        }
        else if (Input.GetMouseButton(1))
        {
            SetTile(worldTilePosition, tileInformations[0].id);
            fluidQueue.Enqueue(new Tuple <int, float>(TileUtil.To1DIndex(worldTilePosition, mapSize), 0.0f));
        }
        else if (Input.GetKeyDown(KeyCode.T))
        {
            SetTile(worldTilePosition, tileInformations[3].id);
        }
        else if (Input.GetKey(KeyCode.W))
        {
            SetTile(worldTilePosition, tileInformations[2].id);
            fluidQueue.Enqueue(new Tuple <int, float>(TileUtil.To1DIndex(worldTilePosition, mapSize), waterDensities[TileUtil.To1DIndex(worldTilePosition, mapSize)] + 3.0f));
        }
    }
    public bool SetTile(int2 worldTilePosition, int id)
    {
        if (!TileUtil.BoundaryCheck(worldTilePosition, mapSize))
        {
            return(false);
        }

        int index = TileUtil.To1DIndex(worldTilePosition, mapSize);

        if (tiles[index] == id)
        {
            return(false);
        }

        int2 chunkPosition = TileUtil.WorldTileToChunk(worldTilePosition, chunkSize);

        if (chunks.TryGetValue(chunkPosition, out TileChunk chunk))
        {
            chunk.SetMeshDirty();
        }
        else
        {
            TileChunk newChunk = GenerateChunk(chunkPosition);
            newChunk.SetMeshDirty();
        }

        if (tileInformations[id].isSolid)
        {
            fluidQueue.Enqueue(new Tuple <int, float>(index, 0.0f));
        }

        LightEmission beforeEmission = tileInformations[tiles[index]].emission;

        tiles[index] = id;

        SetEmission(worldTilePosition, tileInformations[id].emission);
        CheckTileToUpdateLight(worldTilePosition, id, beforeEmission);

        return(true);
    }
        void GenerateMesh(int[] tiles, float[] waterDensities)
        {
            vertices.Clear();
            indices.Clear();
            uvs.Clear();
            colors.Clear();
            paths.Clear();;
            visited.Clear();

            int numQuads = 0;

            for (int x = 0; x < chunkSize.x; x++)
            {
                for (int y = 0; y < chunkSize.y;)
                {
                    int2 tilePosition = TileUtil.TileToWorldTile(new int2(x, y), chunkPosition, chunkSize);
                    int  index        = TileUtil.To1DIndex(tilePosition, mapSize);
                    int  tile         = tiles[index];

                    if (tile == 0)
                    {
                        y++;
                        continue;
                    }

                    if (visited.Contains(tilePosition))
                    {
                        y++;
                        continue;
                    }

                    visited.Add(tilePosition);

                    int height;
                    for (height = 1; height + y < chunkSize.y; height++)
                    {
                        int2 nextPosition = tilePosition + TileUtil.Up * height;

                        if (!TileUtil.BoundaryCheck(nextPosition, chunkPosition, chunkSize))
                        {
                            break;
                        }

                        int nextIndex = TileUtil.To1DIndex(nextPosition, mapSize);

                        int nextTile = tiles[nextIndex];

                        if (nextTile != tile)
                        {
                            break;
                        }

                        if (!TileManager.tileInformations[tile].isSolid)
                        {
                            break;
                        }

                        if (visited.Contains(nextPosition))
                        {
                            break;
                        }

                        visited.Add(nextPosition);
                    }

                    bool done = false;
                    int  width;
                    for (width = 1; width + x < chunkSize.x; width++)
                    {
                        for (int dy = 0; dy < height; dy++)
                        {
                            int2 nextPosition = tilePosition + TileUtil.Up * dy + TileUtil.Right * width;

                            if (!TileUtil.BoundaryCheck(nextPosition, chunkPosition, chunkSize))
                            {
                                done = true;
                                break;
                            }

                            int nextIndex = TileUtil.To1DIndex(nextPosition, mapSize);

                            int nextTile = tiles[nextIndex];

                            if (nextTile != tile || visited.Contains(nextPosition))
                            {
                                done = true;
                                break;
                            }

                            if (!TileManager.tileInformations[tile].isSolid)
                            {
                                done = true;
                                break;
                            }
                        }

                        if (done)
                        {
                            break;
                        }

                        for (int dy = 0; dy < height; dy++)
                        {
                            int2 nextPosition =
                                tilePosition +
                                TileUtil.Up * dy +
                                TileUtil.Right * width;
                            visited.Add(nextPosition);
                        }
                    }

                    float2 scale = new float2(width, height);

                    List <Vector2> points = new List <Vector2>();
                    for (int i = 0; i < 4; i++)
                    {
                        Vector2 vertex = tileVertices[i] * scale + tilePosition;
                        vertices.Add(vertex);

                        Vector2 uv2 = tileVertices[i] * scale;
                        Vector4 uv4 = new Vector4(uv2.x, uv2.y, scale.x, scale.y);
                        uvs.Add(uv4);

                        Color32 color = TileManager.tileInformations[tile].color;
                        if (TileManager.tileInformations[tile].isSolid)
                        {
                            colors.Add(color);
                        }
                        else
                        {
                            float density = Mathf.Clamp(waterDensities[index], 0.0f, 1.0f);

                            int2 upPosition = tilePosition + TileUtil.Up;
                            if (TileUtil.BoundaryCheck(upPosition, mapSize))
                            {
                                int upTile = tiles[TileUtil.To1DIndex(upPosition, mapSize)];
                                if (!TileManager.tileInformations[upTile].isSolid && upTile != 0)
                                {
                                    density = 1.0f;
                                }
                            }

                            int2 downPosition = tilePosition + TileUtil.Down;
                            if (TileUtil.BoundaryCheck(downPosition, mapSize))
                            {
                                int downTile = tiles[TileUtil.To1DIndex(downPosition, mapSize)];
                                if (!TileManager.tileInformations[downTile].isSolid && downTile == 0)
                                {
                                    density = 1.0f;
                                }
                            }

                            color.a = (byte)(byte.MaxValue * density);
                            colors.Add(color);
                        }


                        // Not Optimized Collider Generation
                        Vector2 point = colliderPoints[i] * scale + tilePosition;
                        points.Add(point);
                    }
                    paths.Add(points);

                    for (int i = 0; i < 6; i++)
                    {
                        indices.Add(tileIndices[i] + numQuads * 4);
                    }

                    y += height;
                    numQuads++;
                }
            }
        }
    public void TorchLightPropagation(ref Queue <int2> propagationQueue, ref Queue <Tuple <int2, int> > removalQueue, LightType lightType)
    {
        while (removalQueue.Count != 0)
        {
            (int2 lightPosition, int torchLight) = removalQueue.Dequeue();

            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = lightPosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                int neighborTorchLight = GetLight(neighborPosition, lightType);

                if (neighborTorchLight != 0 && neighborTorchLight < torchLight)
                {
                    SetLight(neighborPosition, 0, lightType);
                    removalQueue.Enqueue(new Tuple <int2, int>(neighborPosition, neighborTorchLight));
                }
                else if (neighborTorchLight >= torchLight)
                {
                    propagationQueue.Enqueue(neighborPosition);
                }
            }
        }

        while (propagationQueue.Count != 0)
        {
            int2 lightPosition = propagationQueue.Dequeue();
            int  torchLight    = GetLight(lightPosition, lightType);

            if (torchLight <= 0)
            {
                continue;
            }

            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = lightPosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                int neighborTorchLight = GetLight(neighborPosition, lightType);

                int resultTorchLight = torchLight - TileLight.SunLightAttenuation;

                int neighborTile = GetTile(neighborPosition);

                bool isOpacity = neighborTile != -1 && neighborTile != 0;

                if (isOpacity)
                {
                    resultTorchLight -= tileInformations[neighborTile].attenuation;
                }

                if (neighborTorchLight >= resultTorchLight)
                {
                    continue;
                }

                SetLight(neighborPosition, resultTorchLight, lightType);
                propagationQueue.Enqueue(neighborPosition);
            }
        }
    }
    public void SunLightPropagation()
    {
        while (sunLightRemovalQueue.Count != 0)
        {
            (int2 lightPosition, int sunLight) = sunLightRemovalQueue.Dequeue();

            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = lightPosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                int neighborSunLight = GetLight(neighborPosition, LightType.S);

                if (neighborSunLight != 0 && neighborSunLight < sunLight || direction.Equals(TileUtil.Down) && sunLight == TileLight.MaxSunLight)
                {
                    SetLight(neighborPosition, 0, LightType.S);
                    sunLightRemovalQueue.Enqueue(new Tuple <int2, int>(neighborPosition, neighborSunLight));
                }
                else if (neighborSunLight >= sunLight)
                {
                    sunLightPropagationQueue.Enqueue(neighborPosition);
                }
            }
        }

        while (sunLightPropagationQueue.Count != 0)
        {
            int2 lightPosition = sunLightPropagationQueue.Dequeue();
            int  sunLight      = GetLight(lightPosition, LightType.S);

            if (sunLight <= 0)
            {
                continue;
            }

            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = lightPosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                int neighborSunLight = GetLight(neighborPosition, LightType.S);

                int resultSunLight = sunLight - TileLight.SunLightAttenuation;

                int neighborTile = GetTile(neighborPosition);

                bool isOpacity = neighborTile != -1 && neighborTile != 0;

                if (isOpacity)
                {
                    resultSunLight -= tileInformations[neighborTile].attenuation;
                }

                if (direction.Equals(TileUtil.Down) && !isOpacity && sunLight == TileLight.MaxSunLight)
                {
                    SetLight(neighborPosition, TileLight.MaxSunLight, LightType.S);
                    sunLightPropagationQueue.Enqueue(neighborPosition);
                }
                else if (neighborSunLight < resultSunLight)
                {
                    SetLight(neighborPosition, resultSunLight, LightType.S);
                    sunLightPropagationQueue.Enqueue(neighborPosition);
                }
            }
        }
    }
    void CheckTileToUpdateLight(int2 worldTilePosition, int id, LightEmission beforeEmission)
    {
        if (id == 0)
        {
            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = worldTilePosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                for (int i = 0; i < 4; i++)
                {
                    LightType lightType     = (LightType)i;
                    int       neighborLight = GetLight(neighborPosition, lightType);

                    if (neighborLight <= 0)
                    {
                        continue;
                    }

                    switch (lightType)
                    {
                    case LightType.S:
                        sunLightPropagationQueue.Enqueue(neighborPosition);
                        break;

                    case LightType.R:
                        torchRedLightPropagationQueue.Enqueue(neighborPosition);
                        break;

                    case LightType.G:
                        torchGreenLightPropagationQueue.Enqueue(neighborPosition);
                        break;

                    case LightType.B:
                        torchBlueLightPropagationQueue.Enqueue(neighborPosition);
                        break;
                    }
                }
            }
        }
        else
        {
            int sunLight = GetLight(worldTilePosition, LightType.S);
            SetLight(worldTilePosition, 0, LightType.S);
            sunLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, sunLight));

            foreach (int2 direction in TileUtil.Direction4)
            {
                int2 neighborPosition = worldTilePosition + direction;

                if (!TileUtil.BoundaryCheck(neighborPosition, mapSize))
                {
                    continue;
                }

                for (int i = 1; i < 4; i++)
                {
                    LightType lightType     = (LightType)i;
                    int       neighborLight = GetLight(neighborPosition, lightType);

                    if (neighborLight <= 0)
                    {
                        continue;
                    }

                    switch (lightType)
                    {
                    case LightType.R:
                        torchRedLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, neighborLight));
                        break;

                    case LightType.G:
                        torchGreenLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, neighborLight));
                        break;

                    case LightType.B:
                        torchBlueLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, neighborLight));
                        break;
                    }
                }
            }
        }

        if (tileInformations[id].emission.r > 0)
        {
            torchRedLightPropagationQueue.Enqueue(worldTilePosition);
        }

        if (tileInformations[id].emission.g > 0)
        {
            torchGreenLightPropagationQueue.Enqueue(worldTilePosition);
        }

        if (tileInformations[id].emission.b > 0)
        {
            torchBlueLightPropagationQueue.Enqueue(worldTilePosition);
        }

        if (beforeEmission.r > 0)
        {
            torchRedLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, beforeEmission.r));
        }

        if (beforeEmission.g > 0)
        {
            torchGreenLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, beforeEmission.g));
        }

        if (beforeEmission.b > 0)
        {
            torchBlueLightRemovalQueue.Enqueue(new Tuple <int2, int>(worldTilePosition, beforeEmission.b));
        }
    }
    IEnumerator UpdateFluid()
    {
        while (fluidQueue.Count != 0)
        {
            (int index, float density) = fluidQueue.Dequeue();

            if (TileUtil.BoundaryCheck(index, mapSize))
            {
                waterDensities[index] = density;
            }
        }

        nativeIndices.Clear();
        nativeTiles.CopyFrom(tiles);
        nativeWaterDensities.CopyFrom(waterDensities);

        for (int i = 0; i < nativeIsSolid.Length; i++)
        {
            nativeIsSolid[i] = tileInformations[i].isSolid;
        }

        InitDiffJob initJob = new InitDiffJob
        {
            waterDiff = nativeWaterDiff
        };

        initJob.Schedule(nativeWaterDiff.Length, 32).Complete();

        InitDirtyJob dirtyJob = new InitDirtyJob
        {
            waterChunkDirty = nativeWaterChunkDirty
        };

        dirtyJob.Schedule(nativeWaterChunkDirty.Length, 32).Complete();
        yield return(null);

        CalculateFluidJob fluidJob = new CalculateFluidJob
        {
            mapSize         = mapSize,
            chunkSize       = chunkSize,
            numChunks       = numChunks,
            maxFlow         = maxFlow,
            minFlow         = minFlow,
            minDensity      = minDensity,
            maxDensity      = maxDensity,
            maxCompress     = maxCompress,
            flowSpeed       = currentFlowSpeed,
            tiles           = nativeTiles,
            waterDensities  = nativeWaterDensities,
            waterDiff       = nativeWaterDiff,
            isSolid         = nativeIsSolid,
            waterChunkDirty = nativeWaterChunkDirty
        };

        fluidJob.Schedule(tiles.Length, 32).Complete();
        yield return(null);

        ApplyFluidJob applyFluidJob = new ApplyFluidJob
        {
            minDensity     = minDensity,
            waterDensities = nativeWaterDensities,
            waterDiff      = nativeWaterDiff
        };

        applyFluidJob.Schedule(waterDensities.Length, 32).Complete();

        nativeWaterDensities.CopyTo(waterDensities);
        yield return(null);

        FilterRemoveFluidJob filterRemoveFluidJob = new FilterRemoveFluidJob
        {
            tiles          = nativeTiles,
            isSolid        = nativeIsSolid,
            minDensity     = minDensity,
            waterDensities = nativeWaterDensities
        };

        filterRemoveFluidJob.ScheduleAppend(nativeIndices, tiles.Length, 32).Complete();
        yield return(null);

        foreach (int fluidIndex in nativeIndices)
        {
            waterDensities[fluidIndex] = 0.0f;
            SetTile(TileUtil.To2DIndex(fluidIndex, mapSize), tileInformations[0].id);
        }

        nativeIndices.Clear();
        FilterCreateFluidJob filterCreateFluidJob = new FilterCreateFluidJob
        {
            tiles          = nativeTiles,
            isSolid        = nativeIsSolid,
            minDensity     = minDensity,
            waterDensities = nativeWaterDensities
        };

        filterCreateFluidJob.ScheduleAppend(nativeIndices, tiles.Length, 32).Complete();
        yield return(null);

        foreach (int fluidIndex in nativeIndices)
        {
            SetTile(TileUtil.To2DIndex(fluidIndex, mapSize), waterID);
        }

        for (int i = 0; i < nativeWaterChunkDirty.Length; i++)
        {
            if (!nativeWaterChunkDirty[i])
            {
                continue;
            }

            if (chunks.TryGetValue(TileUtil.To2DIndex(i, numChunks), out TileChunk chunk))
            {
                chunk.SetMeshDirty();
            }
        }

        fluidUpdator = null;
    }
        public void Execute(int tileIndex)
        {
            int2 tilePosition = TileUtil.To2DIndex(tileIndex, mapSize);

            if (tiles[tileIndex] != waterID)
            {
                if (isSolid[tiles[tileIndex]])
                {
                    waterDensities[tileIndex] = 0.0f;
                }

                return;
            }

            if (waterDensities[tileIndex] < minDensity)
            {
                waterDensities[tileIndex] = 0;
                return;
            }

            float remainingDensity = waterDensities[tileIndex];

            if (remainingDensity <= 0)
            {
                return;
            }

            waterChunkDirty[TileUtil.To1DIndex(TileUtil.WorldTileToChunk(tilePosition, chunkSize), numChunks)] = true;

            int2 downPosition = tilePosition + TileUtil.Down;
            int  downIndex    = TileUtil.To1DIndex(downPosition, mapSize);

            if (TileUtil.BoundaryCheck(downPosition, mapSize) && !isSolid[tiles[downIndex]])
            {
                float flow = CalculateStableDensity(remainingDensity + waterDensities[downIndex]) - waterDensities[downIndex];
                if (flow > minFlow)
                {
                    flow *= flowSpeed;
                }

                flow = Mathf.Clamp(flow, 0, Mathf.Min(maxFlow, remainingDensity));
                waterDiff[tileIndex] -= flow;
                waterDiff[downIndex] += flow;
                remainingDensity     -= flow;
            }

            if (remainingDensity < minDensity)
            {
                waterDiff[tileIndex] -= remainingDensity;
                return;
            }

            int2 leftPosition = tilePosition + TileUtil.Left;
            int  leftIndex    = TileUtil.To1DIndex(leftPosition, mapSize);

            if (TileUtil.BoundaryCheck(leftIndex, mapSize) && !isSolid[tiles[leftIndex]])
            {
                float flow = (remainingDensity - waterDensities[leftIndex]) / 4.0f;
                if (flow > minFlow)
                {
                    flow *= flowSpeed;
                }

                flow = Mathf.Clamp(flow, 0, Mathf.Min(maxFlow, remainingDensity));
                waterDiff[tileIndex] -= flow;
                waterDiff[leftIndex] += flow;
                remainingDensity     -= flow;
            }

            if (remainingDensity < minDensity)
            {
                waterDiff[tileIndex] -= remainingDensity;
                return;
            }

            int2 rightPosition = tilePosition + TileUtil.Right;
            int  rightIndex    = TileUtil.To1DIndex(rightPosition, mapSize);

            if (TileUtil.BoundaryCheck(rightIndex, mapSize) && !isSolid[tiles[rightIndex]])
            {
                float flow = (remainingDensity - waterDensities[rightIndex]) / 3.0f;
                if (flow > minFlow)
                {
                    flow *= flowSpeed;
                }

                flow = Mathf.Clamp(flow, 0, Mathf.Min(maxFlow, remainingDensity));
                waterDiff[tileIndex]  -= flow;
                waterDiff[rightIndex] += flow;
                remainingDensity      -= flow;
            }

            if (remainingDensity < minDensity)
            {
                waterDiff[tileIndex] -= remainingDensity;
                return;
            }

            int2 upPosition = tilePosition + TileUtil.Up;
            int  upIndex    = TileUtil.To1DIndex(upPosition, mapSize);

            if (TileUtil.BoundaryCheck(upIndex, mapSize) && !isSolid[tiles[upIndex]])
            {
                float flow = remainingDensity - CalculateStableDensity(remainingDensity + waterDensities[upIndex]);
                if (flow > minFlow)
                {
                    flow *= flowSpeed;
                }

                flow = Mathf.Clamp(flow, 0, Mathf.Min(maxFlow, remainingDensity));
                waterDiff[tileIndex] -= flow;
                waterDiff[upIndex]   += flow;
                remainingDensity     -= flow;
            }

            if (remainingDensity < minDensity)
            {
                waterDiff[tileIndex] -= remainingDensity;
            }
        }