Esempio n. 1
0
    public static void AddSunlightRemovals(ref NativeArray3x3 <Light> lights, NativeList <TorchLightOp> lightOps, NativeQueue <LightRemovalNode> lrbfs)
    {
        for (int loi = 0; loi < lightOps.Length; ++loi)
        {
            TorchLightOp op = lightOps[loi];

            int opx        = op.index % S;
            int opy        = op.index / (S * S);
            int opz        = (op.index % (S * S)) / S;
            int startIndex = (opx + S) + (opz + S) * W + (opy + S) * W * W;

            Light curLight = lights.Get(opx, opy, opz);
            lrbfs.Enqueue(new LightRemovalNode {
                index = startIndex, light = curLight.sun
            });
            curLight.sun = 0;
            lights.Set(opx, opy, opz, curLight);
        }
    }
Esempio n. 2
0
    static void TrySpawnGemstone(ref NativeArray3x3 <Block> blocks, Random urand, int x, int y, int z)
    {
        //int dir = urand.NextInt(0, 6);
        int3 pos = new int3(x, y, z);
        int3 dir;
        bool stalag = urand.NextFloat() < 0.5f; // ones on ground pointing up

        if (stalag)                             // changed to just be up and down for now see how that looks
        {
            dir = new int3(0, 1, 0);
        }
        else
        {
            dir = new int3(0, -1, 0);
        }

        if (blocks.Get(x - dir.x, y - dir.y, z - dir.z) == Blocks.AIR)
        {
            return;
        }

        int len = urand.NextInt(2, 8 + (int)(math.abs(y) * 0.01f));

        len /= stalag ? 1 : 2;         // stalags should be twice as tall as stalacts (ceiling ones)
        len  = math.clamp(len, 2, 20); // make sure dont get too crazy long

        int sides, xdir, zdir;

        if (len > 2)   // if taller than this have some secondary columns with random lengths
        {
            sides = urand.NextInt(0, 4);
            xdir  = urand.NextInt(2, len - 1);  // size of secondary columns
            zdir  = urand.NextInt(2, len - 1);
        }
        else
        {
            xdir = zdir = sides = 0;
        }
        int   gemType = urand.NextInt(0, 3);
        Block gemBlock;

        if (gemType == 0)
        {
            gemBlock = Blocks.RUBY;
        }
        else if (gemType == 1)
        {
            gemBlock = Blocks.EMERALD;
        }
        else
        {
            gemBlock = Blocks.SAPPHIRE;
        }

        for (int i = 0; i < len; ++i)
        {
            int3 p = pos + dir * i;
            if (blocks.Get(p.x, p.y, p.z) != Blocks.AIR)
            {
                return;
            }
            blocks.Set(p.x, p.y, p.z, gemBlock);

            if (xdir-- > 0)
            {
                if (sides / 2 == 0)   // west
                {
                    if (blocks.Get(p.x - 1, p.y, p.z) == Blocks.AIR)
                    {
                        blocks.Set(p.x - 1, p.y, p.z, gemBlock);
                    }
                }
                else     // east
                {
                    if (blocks.Get(p.x + 1, p.y, p.z) == Blocks.AIR)
                    {
                        blocks.Set(p.x + 1, p.y, p.z, gemBlock);
                    }
                }
            }
            if (zdir-- > 0)
            {
                if (sides % 2 == 0)   // south
                {
                    if (blocks.Get(p.x, p.y, p.z - 1) == Blocks.AIR)
                    {
                        blocks.Set(p.x, p.y, p.z - 1, gemBlock);
                    }
                }
                else     // north
                {
                    if (blocks.Get(p.x, p.y, p.z + 1) == Blocks.AIR)
                    {
                        blocks.Set(p.x, p.y, p.z + 1, gemBlock);
                    }
                }
            }
        }
    }
Esempio n. 3
0
    static void TrySpawnTree(ref NativeArray3x3 <Block> blocks, Random urand, int x, int y, int z)
    {
        int width = urand.NextInt(1, 4);

        int height = 0;

        if (width == 1)
        {
            height = urand.NextInt(3, 10);
            for (int i = 0; i <= height; ++i)
            {
                Block b = blocks.Get(x, y + i, z);
                if (i == 0)
                {
                    if (b != Blocks.GRASS)
                    {
                        return;
                    }
                }
                else
                {
                    if (b != Blocks.AIR)
                    {
                        return;
                    }
                }
            }
        }
        else if (width == 2)
        {
            height = urand.NextInt(8, 20);
            for (int i = 0; i <= height; ++i)
            {
                for (int u = 0; u <= 1; ++u)
                {
                    for (int v = 0; v <= 1; ++v)
                    {
                        Block b = blocks.Get(x + u, y + i, z + v);
                        if (i == 0)
                        {
                            if (b != Blocks.GRASS)
                            {
                                return;
                            }
                        }
                        else
                        {
                            if (b != Blocks.AIR)
                            {
                                return;
                            }
                        }
                    }
                }
            }
        }
        else if (width == 3)
        {
            height = urand.NextInt(16, 30);
            for (int i = 0; i <= height; ++i)
            {
                for (int u = -1; u <= 1; ++u)
                {
                    for (int v = -1; v <= 1; ++v)
                    {
                        Block b = blocks.Get(x + u, y + i, z + v);
                        if (i == 0)
                        {
                            if (b != Blocks.GRASS)
                            {
                                return;
                            }
                        }
                        else
                        {
                            if (b != Blocks.AIR)
                            {
                                return;
                            }
                        }
                    }
                }
            }
        }

        for (int i = 1; i <= height + 1; ++i)
        {
            float f  = (height - i);
            float hf = (float)i / height;
            hf  = 1.0f - hf;
            hf *= hf;
            int s = (int)(f * hf);
            if (s <= 0)
            {
                s = 1;
            }
            s += urand.NextInt(-1, 2);
            int us = -s;
            int vs = -s;
            if (width == 3)
            {
                us -= 1;
                vs -= 1;
            }

            for (int u = us; u <= s + width - 1; ++u)
            {
                for (int v = vs; v <= s + width - 1; ++v)
                {
                    // spawn trunk if near center
                    if (width == 1 && u == 0 && v == 0 ||
                        width == 2 && (u >= 0 && u <= 1) && (v >= 0 && v <= 1) ||
                        width == 3 && (u >= -1 && u <= 1) && (v >= -1 && v <= 1))
                    {
                        blocks.Set(x + u, y + i, z + v, Blocks.PINE);
                    }
                    else if (i >= width * 2 && i % 2 == 0 || i > height - 1)     // otherwise leaves
                    {
                        if (blocks.Get(x + u, y + i, z + v) == Blocks.AIR)
                        {
                            blocks.Set(x + u, y + i, z + v, Blocks.PINELEAF);
                        }
                    }
                }
            }
        }
    }
Esempio n. 4
0
    // this is a pine tree routine
    static void TrySpawnGoodTree(ref NativeArray3x3 <Block> blocks, Random urand, int x, int y, int z)
    {
        float width = urand.NextFloat(0.5f, 2f);

        int height = 0;

        height = (int)(28 * width / 2.0f) + urand.NextInt(-3, 3);
        math.clamp(height, 5, 29);

        // make sure space is kinda clear for tree height at least
        int wi = (int)width + 1;

        for (int u = -wi; u <= wi; ++u)
        {
            for (int v = -wi; v <= wi; ++v)
            {
                if (math.lengthsq(new float2(u, v)) < wi * wi)
                {
                    if (blocks.Get(x + u, y, z + v) != Blocks.GRASS)
                    {
                        return;
                    }
                }
            }
        }
        for (int i = 1; i <= height; ++i)
        {
            if (blocks.Get(x, y + i, z) != Blocks.AIR)
            {
                return;
            }
        }

        int nextLeafLevel = urand.NextInt(3, 5);

        for (int i = 1; i <= height; ++i)
        {
            float hp       = (float)i / height;
            float curWidth = math.lerp(width, width / 2.0f, hp);
            float leafEdge = 0.0f;

            //bool leafLayer = (i >= 3 && i % 2 == 0) || i >= height;
            bool leafLayer = i == nextLeafLevel || i == height;
            if (leafLayer)
            {
                leafEdge = math.lerp(width * 4.0f, 1.5f, Mth.QuadraticEaseOut(hp));
                float r = urand.NextFloat();
                if (r < 0.1f)
                {
                    nextLeafLevel += 1;
                }
                else if (r < 0.75f || height < 20)
                {
                    nextLeafLevel += 2;
                }
                else     // taller trees have small chance of large gaps in leaves
                {
                    nextLeafLevel += 3;
                }
                if (i == height)
                {
                    leafEdge = 1.0f;
                }
                else if (urand.NextFloat() < .2f)
                {
                    leafEdge++;
                }
            }
            int cwi = (int)(curWidth + leafEdge) + 1;

            for (int u = -cwi; u <= cwi; ++u)
            {
                for (int v = -cwi; v <= cwi; ++v)
                {
                    float len = math.lengthsq(new float2(u, v));
                    if (len < curWidth * curWidth)
                    {
                        blocks.Set(x + u, y + i, z + v, Blocks.PINE);
                    }
                    else if (leafLayer && len < (curWidth + leafEdge) * (curWidth + leafEdge))
                    {
                        if (blocks.Get(x + u, y + i, z + v) == Blocks.AIR)
                        {
                            blocks.Set(x + u, y + i, z + v, Blocks.PINELEAF);
                        }
                    }
                }
            }
        }

        // add leaves to the top
        int topper = height > 20 ? 2 : 1;

        for (int i = height + 1; i < height + 1 + topper; ++i)
        {
            if (blocks.Get(x, y + i, z) == Blocks.AIR)
            {
                blocks.Set(x, y + i, z, Blocks.PINELEAF);
            }
        }
    }
Esempio n. 5
0
    public static void ProcessSunlight(ref NativeArray3x3 <Light> lights, ref NativeArray3x3 <Block> blocks, NativeArray <BlockData> blockData,
                                       NativeQueue <int> lbfs, NativeQueue <int> lbfs_U, NativeQueue <LightRemovalNode> lrbfs, NativeQueue <LightRemovalNode> lrbfs_U)
    {
        // not sure if going to have sunlightops... the ordering might get boned up on quick place delete operations, but way more efficient this way
        //for (int oi = 0; oi < ops.Length;) {
        //    // check to see what type of operation first op is
        //    bool isProp = ops[oi].val != 0;
        //    // queue up each matching operation type until you hit end or mismatch
        //    while (oi < ops.Length) {
        //        SunLightOp op = ops[oi];
        //        if ((op.val != 0) != isProp) {
        //            break; // this ops type is different from first in run
        //        }
        //        int opx = op.index % S;
        //        int opy = op.index / (S * S);
        //        int opz = (op.index % (S * S)) / S;
        //        int startIndex = (opx + S) + (opz + S) * W + (opy + S) * W * W;

        //        Light curLight = lights.Get(opx, opy, opz);
        //        if (isProp) {
        //            // set the new light value here
        //            Light newLight = curLight;
        //            newLight.sun = op.val;
        //            lights.Set(opx, opy, opz, newLight);

        //            // if the new sun value is same or less than current dont need to progagate
        //            // this might not happen with sunlight but left check anyways
        //            if (op.val <= curLight.sun) {
        //                continue;
        //            }
        //        } else {
        //            lrbfs.Enqueue(new LightRemovalNode { index = startIndex, light = curLight.sun });
        //            curLight.sun = 0;
        //            lights.Set(opx, opy, opz, curLight);
        //        }

        //        oi++;
        //    }

        //if (!isProp) {
        while (lrbfs.Count > 0)
        {
            LightRemovalNode node = lrbfs.Dequeue();

            // extract coords from index
            int x = node.index % W - S;
            int y = node.index / (W * W) - S;
            int z = (node.index % (W * W)) / W - S;

            byte oneLess = (byte)(node.light - 1); // each time reduce light by one

            Light light = lights.Get(x - 1, y, z);
            if (light.sun != 0)   // WEST
            {
                int index = x - 1 + S + (z + S) * W + (y + S) * W * W;
                if (light.sun < node.light)
                {
                    light.sun = 0;
                    lights.Set(x - 1, y, z, light);
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = oneLess
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }

            light = lights.Get(x, y - 1, z);
            if (light.sun != 0)   // DOWN
            {
                int index = x + S + (z + S) * W + (y - 1 + S) * W * W;
                if (light.sun < node.light || node.light == MAX_LIGHT)
                {
                    light.sun = 0;
                    lights.Set(x, y - 1, z, light);
                    byte lv = node.light == MAX_LIGHT ? MAX_LIGHT : oneLess;
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = lv
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }

            light = lights.Get(x, y, z - 1);
            if (light.sun != 0)   // SOUTH
            {
                int index = x + S + (z - 1 + S) * W + (y + S) * W * W;
                if (light.sun < node.light)
                {
                    light.sun = 0;
                    lights.Set(x, y, z - 1, light);
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = oneLess
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }

            light = lights.Get(x + 1, y, z);
            if (light.sun != 0)   // EAST
            {
                int index = x + 1 + S + (z + S) * W + (y + S) * W * W;
                if (light.sun < node.light)
                {
                    light.sun = 0;
                    lights.Set(x + 1, y, z, light);
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = oneLess
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }

            light = lights.Get(x, y + 1, z);
            if (light.sun != 0)   // UP
            {
                int index = x + S + (z + S) * W + (y + 1 + S) * W * W;
                if (light.sun < node.light)
                {
                    light.sun = 0;
                    lights.Set(x, y + 1, z, light);
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = oneLess
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }

            light = lights.Get(x, y, z + 1);
            if (light.sun != 0)   // NORTH
            {
                int index = x + S + (z + 1 + S) * W + (y + S) * W * W;
                if (light.sun < node.light)
                {
                    light.sun = 0;
                    lights.Set(x, y, z + 1, light);
                    lrbfs.Enqueue(new LightRemovalNode {
                        index = index, light = oneLess
                    });
                }
                else     // add to propagate queue so can fill gaps left behind by removal
                {
                    lbfs.Enqueue(index);
                }
            }
        }

        // propagate (either way)
        while (lbfs.Count > 0)
        {
            int index = lbfs.Dequeue();

            // extract coords from index
            int x = index % W - S;
            int y = index / (W * W) - S;
            int z = (index % (W * W)) / W - S;

            // get light level at this node
            int sunLight = lights.Get(x, y, z).sun;

            // check each neighbor blocks light reduction value
            // if neighbor light level is 2 or more levels less than this node, set them to this light-1 and add to queue
            // also add additional light reduction value
            byte LR = blockData[blocks.Get(x - 1, y, z).type].lightReduction;
            if (LR < sunLight)   // WEST
            {
                Light light = lights.Get(x - 1, y, z);
                if (light.sun + 2 + LR <= sunLight)
                {
                    light.sun = (byte)(sunLight - 1 - LR);
                    lights.Set(x - 1, y, z, light);
                    lbfs.Enqueue(x - 1 + S + (z + S) * W + (y + S) * W * W);
                }
            }
            LR = blockData[blocks.Get(x, y - 1, z).type].lightReduction;
            if (LR < sunLight)   // DOWN
            {
                Light light = lights.Get(x, y - 1, z);
                if (light.sun + 2 + LR <= sunLight)
                {
                    if (sunLight == MAX_LIGHT)   // if at maxlight dont reduce by 1 each time
                    {
                        light.sun = (byte)(sunLight - LR);
                    }
                    else
                    {
                        light.sun = (byte)(sunLight - 1 - LR);
                    }
                    lights.Set(x, y - 1, z, light);
                    if (y <= -31)
                    {
                        lbfs_U.Enqueue(x + S + (z + S) * W + (32 + S) * W * W); // add to unfinished queue and shift index to be proper for downdown chunk
                    }
                    else
                    {
                        lbfs.Enqueue(x + S + (z + S) * W + (y - 1 + S) * W * W);
                    }
                }
            }
            LR = blockData[blocks.Get(x, y, z - 1).type].lightReduction;
            if (LR < sunLight)   // SOUTH
            {
                Light light = lights.Get(x, y, z - 1);
                if (light.sun + 2 + LR <= sunLight)
                {
                    light.sun = (byte)(sunLight - 1 - LR);
                    lights.Set(x, y, z - 1, light);
                    lbfs.Enqueue(x + S + (z - 1 + S) * W + (y + S) * W * W);
                }
            }
            LR = blockData[blocks.Get(x + 1, y, z).type].lightReduction;
            if (LR < sunLight)   // EAST
            {
                Light light = lights.Get(x + 1, y, z);
                if (light.sun + 2 + LR <= sunLight)
                {
                    light.sun = (byte)(sunLight - 1 - LR);
                    lights.Set(x + 1, y, z, light);
                    lbfs.Enqueue(x + 1 + S + (z + S) * W + (y + S) * W * W);
                }
            }
            LR = blockData[blocks.Get(x, y + 1, z).type].lightReduction;
            if (LR < sunLight)   // UP
            {
                Light light = lights.Get(x, y + 1, z);
                if (light.sun + 2 + LR <= sunLight)
                {
                    light.sun = (byte)(sunLight - 1 - LR);
                    lights.Set(x, y + 1, z, light);
                    lbfs.Enqueue(x + S + (z + S) * W + (y + 1 + S) * W * W);
                }
            }
            LR = blockData[blocks.Get(x, y, z + 1).type].lightReduction;
            if (LR < sunLight)   // NORTH
            {
                Light light = lights.Get(x, y, z + 1);
                if (light.sun + 2 + LR <= sunLight)
                {
                    light.sun = (byte)(sunLight - 1 - LR);
                    lights.Set(x, y, z + 1, light);
                    lbfs.Enqueue(x + S + (z + 1 + S) * W + (y + S) * W * W);
                }
            }
        }
    }
Esempio n. 6
0
    public static void ProcessTorchLightOpsOptimal(ref NativeArray3x3 <Light> lights, ref NativeArray3x3 <Block> blocks, NativeArray <BlockData> blockData, NativeList <TorchLightOp> ops, NativeQueue <int> lbfs, NativeQueue <LightRemovalNode> lrbfs)
    {
        lights.flags = 0;

        // set blocks at torchlightops to have correct is light flag set
        for (int opIndex = 0; opIndex < ops.Length; ++opIndex)
        {
            TorchLightOp op      = ops[opIndex];
            int          opx     = op.index % S;
            int          opy     = op.index / (S * S);
            int          opz     = (op.index % (S * S)) / S;
            Light        opLight = lights.Get(opx, opy, opz);
            opLight.torch = SetIsLight(opLight.torch, op.val > 0);
            lights.Set(opx, opy, opz, opLight);
        }

        // loop over each color channel of torch light
        for (int cIndex = 0; cIndex < 3; cIndex++)
        {
            for (int oi = 0; oi < ops.Length;)
            {
                // check to see what type of operation first op is
                bool isProp = GetChannel(ops[oi].val, cIndex) != 0;
                // queue up each matching operation type until you hit end or mismatch
                while (oi < ops.Length)
                {
                    TorchLightOp op        = ops[oi];
                    int          opChannel = GetChannel(op.val, cIndex);
                    if ((opChannel != 0) != isProp)
                    {
                        break; // this ops type is different from first in run
                    }
                    int opx        = op.index % S;
                    int opy        = op.index / (S * S);
                    int opz        = (op.index % (S * S)) / S;
                    int startIndex = (opx + S) + (opz + S) * W + (opy + S) * W * W;

                    Light curLight   = lights.Get(opx, opy, opz);
                    int   curChannel = GetChannel(curLight.torch, cIndex);
                    if (isProp)
                    {
                        // set the new light value here
                        Light newLight = curLight;
                        newLight.torch = SetChannel(newLight.torch, cIndex, opChannel);
                        lights.Set(opx, opy, opz, newLight);

                        // if the new ops channel value is same as current light channel then add to propagate
                        if (opChannel > curChannel)
                        {
                            lbfs.Enqueue(startIndex);
                        }
                    }
                    else
                    {
                        lrbfs.Enqueue(new LightRemovalNode {
                            index = startIndex, light = (byte)curChannel
                        });
                        curLight.torch = SetChannel(curLight.torch, cIndex, 0);
                        lights.Set(opx, opy, opz, curLight);
                    }

                    oi++;
                }

                if (!isProp)
                {
                    while (lrbfs.Count > 0)
                    {
                        LightRemovalNode node = lrbfs.Dequeue();

                        // extract coords from index
                        int x = node.index % W - S;
                        int y = node.index / (W * W) - S;
                        int z = (node.index % (W * W)) / W - S;

                        byte oneLess = (byte)(node.light - 1); // each time reduce light by one

                        //ushort westLight = light.Get(x - 1, y, z).torch;
                        Light westLight   = lights.Get(x - 1, y, z);
                        byte  westChannel = (byte)GetChannel(westLight.torch, cIndex);
                        if (westChannel != 0)
                        {
                            int index = x - 1 + S + (z + S) * W + (y + S) * W * W;
                            if (westChannel < node.light)
                            {
                                if (!GetIsLight(westLight.torch))
                                {
                                    westLight.torch = SetChannel(westLight.torch, cIndex, 0);
                                    lights.Set(x - 1, y, z, westLight);
                                }
                                else     // if this node is a light, dont override value, but still add a removal node as if you did, then add to repropagate to fill it back in
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }

                        Light downLight   = lights.Get(x, y - 1, z);
                        byte  downChannel = (byte)GetChannel(downLight.torch, cIndex);
                        if (downChannel != 0)
                        {
                            int index = x + S + (z + S) * W + (y - 1 + S) * W * W;
                            if (downChannel < node.light)
                            {
                                if (!GetIsLight(downLight.torch))
                                {
                                    downLight.torch = SetChannel(downLight.torch, cIndex, 0);
                                    lights.Set(x, y - 1, z, downLight);
                                }
                                else
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }

                        Light southLight   = lights.Get(x, y, z - 1);
                        byte  southChannel = (byte)GetChannel(southLight.torch, cIndex);
                        if (southChannel != 0)
                        {
                            int index = x + S + (z - 1 + S) * W + (y + S) * W * W;
                            if (southChannel < node.light)
                            {
                                if (!GetIsLight(southLight.torch))
                                {
                                    southLight.torch = SetChannel(southLight.torch, cIndex, 0);
                                    lights.Set(x, y, z - 1, southLight);
                                }
                                else
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }

                        Light eastLight   = lights.Get(x + 1, y, z);
                        byte  eastChannel = (byte)GetChannel(eastLight.torch, cIndex);
                        if (eastChannel != 0)
                        {
                            int index = x + 1 + S + (z + S) * W + (y + S) * W * W;
                            if (eastChannel < node.light)
                            {
                                if (!GetIsLight(eastLight.torch))
                                {
                                    eastLight.torch = SetChannel(eastLight.torch, cIndex, 0);
                                    lights.Set(x + 1, y, z, eastLight);
                                }
                                else
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }

                        Light upLight   = lights.Get(x, y + 1, z);
                        byte  upChannel = (byte)GetChannel(upLight.torch, cIndex);
                        if (upChannel != 0)
                        {
                            int index = x + S + (z + S) * W + (y + 1 + S) * W * W;
                            if (upChannel < node.light)
                            {
                                if (!GetIsLight(upLight.torch))
                                {
                                    upLight.torch = SetChannel(upLight.torch, cIndex, 0);
                                    lights.Set(x, y + 1, z, upLight);
                                }
                                else
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }

                        Light northLight   = lights.Get(x, y, z + 1);
                        byte  northChannel = (byte)GetChannel(northLight.torch, cIndex);
                        if (northChannel != 0)
                        {
                            int index = x + S + (z + 1 + S) * W + (y + S) * W * W;
                            if (northChannel < node.light)
                            {
                                if (!GetIsLight(northLight.torch))
                                {
                                    northLight.torch = SetChannel(northLight.torch, cIndex, 0);
                                    lights.Set(x, y, z + 1, northLight);
                                }
                                else
                                {
                                    lbfs.Enqueue(index);
                                }
                                lrbfs.Enqueue(new LightRemovalNode {
                                    index = index, light = oneLess
                                });
                            }
                            else     // add to propagate queue so can fill gaps left behind by removal
                            {
                                lbfs.Enqueue(index);
                            }
                        }
                    }
                }

                // propagate either way
                while (lbfs.Count > 0)
                {
                    int index = lbfs.Dequeue();

                    // extract coords from index
                    int x = index % W - S;
                    int y = index / (W * W) - S;
                    int z = (index % (W * W)) / W - S;

                    // get light level at this node
                    int mChan = GetChannel(lights.Get(x, y, z).torch, cIndex);

                    //if(mChan == 0) { // can happen frequently when batching together light removals
                    //    continue;
                    //}

                    // check each neighbor blocks light reduction value
                    // if neighbor light level is 2 or more levels less than this node, set them to this light-1 and add to queue
                    // also add additional light reduction value
                    byte LR = blockData[blocks.Get(x - 1, y, z).type].lightReduction;
                    if (LR < mChan)   // WEST
                    {
                        Light light = lights.Get(x - 1, y, z);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x - 1, y, z, light);
                            lbfs.Enqueue(x - 1 + S + (z + S) * W + (y + S) * W * W);
                        }
                    }
                    LR = blockData[blocks.Get(x, y - 1, z).type].lightReduction;
                    if (LR < mChan)   // DOWN
                    {
                        Light light = lights.Get(x, y - 1, z);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x, y - 1, z, light);
                            lbfs.Enqueue(x + S + (z + S) * W + (y - 1 + S) * W * W);
                        }
                    }
                    LR = blockData[blocks.Get(x, y, z - 1).type].lightReduction;
                    if (LR < mChan)   // SOUTH
                    {
                        Light light = lights.Get(x, y, z - 1);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x, y, z - 1, light);
                            lbfs.Enqueue(x + S + (z - 1 + S) * W + (y + S) * W * W);
                        }
                    }
                    LR = blockData[blocks.Get(x + 1, y, z).type].lightReduction;
                    if (LR < mChan)   // EAST
                    {
                        Light light = lights.Get(x + 1, y, z);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x + 1, y, z, light);
                            lbfs.Enqueue(x + 1 + S + (z + S) * W + (y + S) * W * W);
                        }
                    }
                    LR = blockData[blocks.Get(x, y + 1, z).type].lightReduction;
                    if (LR < mChan)   // UP
                    {
                        Light light = lights.Get(x, y + 1, z);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x, y + 1, z, light);
                            lbfs.Enqueue(x + S + (z + S) * W + (y + 1 + S) * W * W);
                        }
                    }
                    LR = blockData[blocks.Get(x, y, z + 1).type].lightReduction;
                    if (LR < mChan)   // NORTH
                    {
                        Light light = lights.Get(x, y, z + 1);
                        if (GetChannel(light.torch, cIndex) + 2 + LR <= mChan)
                        {
                            light.torch = SetChannel(light.torch, cIndex, mChan - 1 - LR);
                            lights.Set(x, y, z + 1, light);
                            lbfs.Enqueue(x + S + (z + 1 + S) * W + (y + S) * W * W);
                        }
                    }
                }
            }
        }
    }