/// <summary>
        /// Divide scope into portions as described by divisors list.
        /// Add snap planes at the dividend bounds if a snap id is given in the divisor.
        /// If any snap planes in the selector list are found within a dividend scope, snap the bounds to match the nearest snap plane of that selector type.
        /// </summary>
        /// <param name="divider"> </param>
        /// <returns></returns>
        public override ProductionScope[] Divide(DivideProduction divider)
        {
            // step 1: validate divisor magnitudes. scopeLength > absolute length; scopeLength - absolute lengths = availableRelLength
            var absSpan = divider.Divisors.Where(div => div.IsAbsolute).Sum(div => div.Magnitude);
            var relSpan = divider.Divisors.Where(div => !div.IsAbsolute).Sum(div => div.Magnitude);

            if (Math.Abs(relSpan - 1) > Mathf.Epsilon || absSpan > Bounds.size[(int)divider.DivisionAxis])
            {
                throw new Exception("Division length is invalid.");
            }

            var divAxisExtent = Vector3.zero;

            divAxisExtent[(int)divider.DivisionAxis] = Bounds.extents[(int)divider.DivisionAxis];
            var frontierPoint = Bounds.center - Rotation * (divAxisExtent);

            var dividends = new ProductionScope[divider.Divisors.Count()];

            for (var i = 0; i < divider.Divisors.Length; i++)
            {
                var divisor = divider.Divisors[i];

                // slice volume along our division axis, slicing off [magnitude] volume or [magnitude]% of the volume available for relative divisions
                Vector3 dividendSpan = Vector3.zero;
                dividendSpan[(int)divider.DivisionAxis] = divisor.IsAbsolute
                                               ? divisor.Magnitude
                                               : Bounds.size[(int)divider.DivisionAxis] * divisor.Magnitude;

                // compute dividend volume's center; just move from frontier point (min) to terminal point (max) along the division axis
                var dividendCenter = frontierPoint + (Rotation * (dividendSpan / 2f));
                var s = Bounds.size;
                s[(int)divider.DivisionAxis] = dividendSpan[(int)divider.DivisionAxis];
                var proposedBounds = new Bounds(dividendCenter, s);

                dividends[i] = new CubeProductionScope(SnapBoundsToSnapPlanes(divider.DivisionAxis, SelectSnapPlanes(divider.DivisionAxis, divider.SnapToPlanes), proposedBounds), Rotation);

                if (!string.IsNullOrEmpty(divisor.SnapPlaneKey))
                {
                    // add another snap plane to the module
                    AddSnapPlane(divider.DivisionAxis, divisor.SnapPlaneKey, dividends[i].Bounds);
                }
                if (dividends[i].Bounds.size.x <= 0 || dividends[i].Bounds.size.y <= 0 || dividends[i].Bounds.size.z <= 0)
                {
                    Debug.Log("Dividing to a negative size...");
                }
                // update frontier point
                frontierPoint = frontierPoint + Rotation * dividendSpan;
            }

            return(dividends);
        }
        /// <summary>
        /// Create scopes for each repetition of the repeat production.
        /// Add snap planes at the replicand bounds if a snap id is given in the repeater.
        /// If any snap planes in the selector list are found within a replicand scope, snap the bounds to match the nearest snap plane of that selector type.
        ///
        /// Some replicand scopes may vary in size depending on the volume remainder and a specified remainder mode.
        /// Remainders below RepeatEpsilon epsilon are always distributed (to avoid teeny-tiny inserts).
        /// </summary>
        /// <param name="repeater"> </param>
        /// <returns></returns>
        public override ProductionScope[] Repeat(RepeatProduction repeater)
        {
            if (repeater.Magnitude > Bounds.size[(int)repeater.RepetitionAxis] || Math.Abs(repeater.Magnitude - 0) < Mathf.Epsilon)
            {
                throw new Exception("Repetition length is invalid.");
            }

            var repetitionAxisExtent = Vector3.zero;

            repetitionAxisExtent[(int)repeater.RepetitionAxis] = Bounds.extents[(int)repeater.RepetitionAxis];
            var frontierPoint = Bounds.center - Rotation * (repetitionAxisExtent);

            var repetitions  = Bounds.size[(int)repeater.RepetitionAxis] / repeater.Magnitude;
            var repRemainder = repetitions - (int)repetitions;

            Vector3 firstRepSize;
            var     replicandSize = Vector3.zero;
            Vector3 lastRepSize;

            ProductionScope[] replicands;
            if (repeater.RemainderMode == RepeatRemainderMode.DistributeRemainder || repRemainder < RepetitionEpsilon)
            {
                // evenly distribute remainder (if any) to replicands
                replicands = new ProductionScope[(int)repetitions];

                replicandSize[(int)repeater.RepetitionAxis] = Bounds.size[(int)repeater.RepetitionAxis] / repetitions;
                firstRepSize = replicandSize;
                lastRepSize  = replicandSize;
            }
            else
            {
                replicandSize[(int)repeater.RepetitionAxis] = repeater.Magnitude;
                firstRepSize = replicandSize;
                lastRepSize  = replicandSize;

                if (repeater.RemainderMode == RepeatRemainderMode.MergeFirst ||
                    repeater.RemainderMode == RepeatRemainderMode.MergeLast ||
                    repeater.RemainderMode == RepeatRemainderMode.MergeFirstAndLast)
                {
                    replicands = new ProductionScope[(int)repetitions];

                    if (repeater.RemainderMode == RepeatRemainderMode.MergeFirst)
                    {
                        firstRepSize[(int)repeater.RepetitionAxis] = replicandSize[(int)repeater.RepetitionAxis] + Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude;
                    }
                    else if (repeater.RemainderMode == RepeatRemainderMode.MergeLast)
                    {
                        lastRepSize[(int)repeater.RepetitionAxis] = replicandSize[(int)repeater.RepetitionAxis] + Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude;
                    }
                    else // if (repeater.RemainderMode == RepeatRemainderMode.MergeFirstAndLast)
                    {
                        firstRepSize[(int)repeater.RepetitionAxis] = replicandSize[(int)repeater.RepetitionAxis] + (Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude / 2);
                        lastRepSize[(int)repeater.RepetitionAxis]  = replicandSize[(int)repeater.RepetitionAxis] + (Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude / 2);
                    }
                }
                else
                {
                    //if (repeater.RemainderMode == RepeatRemainderMode.InsertFirst || repeater.RemainderMode == RepeatRemainderMode.InsertLast || repeater.RemainderMode == RepeatRemainderMode.InsertFirstAndLast)
                    replicands = new ProductionScope[(int)repetitions + 1];

                    if (repeater.RemainderMode == RepeatRemainderMode.InsertFirst)
                    {
                        // insert replicand for remainder at first position
                        firstRepSize[(int)repeater.RepetitionAxis] = Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude;
                    }
                    else if (repeater.RemainderMode == RepeatRemainderMode.InsertLast)
                    {
                        // insert replicand for remainder at last position
                        lastRepSize[(int)repeater.RepetitionAxis] = Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude;
                    }
                    else //if (repeater.RemainderMode == RepeatRemainderMode.InsertFirstAndLast)
                    {
                        // insert replicands for remainder at first and last positions
                        replicands = new ProductionScope[(int)repetitions + 2];

                        firstRepSize[(int)repeater.RepetitionAxis] = Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude / 2;
                        lastRepSize[(int)repeater.RepetitionAxis]  = Bounds.size[(int)repeater.RepetitionAxis] % repeater.Magnitude / 2;
                    }
                }
            }

            for (var i = 0; i < replicands.Length; ++i)
            {
                Vector3 replicandCenter;
                Bounds  proposedBounds;

                if (i == 0)
                {
                    replicandCenter = frontierPoint + Rotation * (firstRepSize / 2f);
                    frontierPoint   = frontierPoint + Rotation * (firstRepSize);
                    var s = Bounds.size;
                    s[(int)repeater.RepetitionAxis] = firstRepSize[(int)repeater.RepetitionAxis];
                    proposedBounds = new Bounds(replicandCenter, s);
                }
                else if (i == replicands.Length - 1)
                {
                    replicandCenter = frontierPoint + Rotation * (lastRepSize / 2f);
                    frontierPoint   = frontierPoint + Rotation * (lastRepSize);
                    var s = Bounds.size;
                    s[(int)repeater.RepetitionAxis] = lastRepSize[(int)repeater.RepetitionAxis];
                    proposedBounds = new Bounds(replicandCenter, s);
                }
                else
                {
                    // compute dividend volume's center; just move from frontier point (min) to terminal point (max) along the division axis
                    replicandCenter = frontierPoint + Rotation * (replicandSize / 2f);
                    frontierPoint   = frontierPoint + Rotation * (replicandSize);
                    var s = Bounds.size;
                    s[(int)repeater.RepetitionAxis] = replicandSize[(int)repeater.RepetitionAxis];
                    proposedBounds = new Bounds(replicandCenter, s);
                }

                // todo: will also need to update frontier point in a case where bounds get snapped to some plane
                replicands[i] = new CubeProductionScope(
                    SnapBoundsToSnapPlanes(repeater.RepetitionAxis, SelectSnapPlanes(repeater.RepetitionAxis, repeater.SnapToPlanes), proposedBounds),
                    Rotation);

                if (!string.IsNullOrEmpty(repeater.SnapPlaneKey))
                {
                    // add another snap plane to the module
                    AddSnapPlane(repeater.RepetitionAxis, repeater.SnapPlaneKey, replicands[i].Bounds);
                }
            }

            return(replicands);
        }