private static void DrawFieldSprtDoIt(byte[] pixels, double[] ink, bool[] blocked, int size, byte[] colorZFront, byte[] colorZBack, AxisFor pixelX, AxisFor pixelY, AxisFor pixelZ)
        {
            List<Mapping_2D_1D> flattened = new List<Mapping_2D_1D>();

            // Setup the pixel array
            //for (int y2D = pixelY.Start; pixelY.IsPos ? y2D <= pixelY.Stop : y2D >= pixelY.Stop; y2D += pixelY.Increment)
            foreach (int y2D in pixelY.Iterate())
            {
                int offsetY = pixelY.GetValueForOffset(y2D) * size;

                //for (int x2D = pixelX.Start; pixelX.IsPos ? x2D <= pixelX.Stop : x2D >= pixelX.Stop; x2D += pixelX.Increment)
                foreach (int x2D in pixelX.Iterate())
                {
                    int offset = offsetY + pixelX.GetValueForOffset(x2D);

                    flattened.Add(new Mapping_2D_1D(x2D, y2D, offset));
                }
            }

            // Each pixel of the output bitmap can be added up independently, so employ some threading
            flattened.AsParallel().ForAll(o =>
                {
                    //TODO: Color is 6.5 times slower than byte array
                    List<byte[]> colorColumn = new List<byte[]>();

                    for (int z2D = pixelZ.Start; pixelZ.IsPos ? z2D <= pixelZ.Stop : z2D >= pixelZ.Stop; z2D += pixelZ.Increment)
                    {
                        int x = -1, y = -1, z = -1;
                        pixelX.Set3DIndex(ref x, ref y, ref z, o.X);
                        pixelY.Set3DIndex(ref x, ref y, ref z, o.Y);
                        pixelZ.Set3DIndex(ref x, ref y, ref z, z2D);

                        int index = FluidField3D.Get1DIndex(x, y, z, size);
                        if (blocked[index])
                        {
                            // Blocked cells are all white, so save the overlay method a bit of work, and throw out everything behind this
                            colorColumn.Clear();
                            colorColumn.Add(new byte[] { 255, 255, 255, 255 });
                            continue;
                        }

                        double inkCell = ink[index];

                        if (Math1D.IsNearZero(inkCell))
                        {
                            continue;
                        }

                        byte[] depthColor = UtilityWPF.AlphaBlend(colorZBack, colorZFront, UtilityCore.GetScaledValue_Capped(0, 1, 0, size - 1, z));

                        int alpha = Convert.ToInt32(Math.Round(inkCell * 255));
                        if (alpha < 0)
                        {
                            alpha = 0;
                        }
                        else if (alpha > 255)
                        {
                            alpha = 255;
                        }

                        colorColumn.Add(new byte[] { Convert.ToByte(alpha), depthColor[1], depthColor[2], depthColor[3] });
                    }

                    byte[] color = colorColumn.Count > 0 ? UtilityWPF.OverlayColors(colorColumn) : new byte[] { 0, 0, 0, 0 };

                    pixels[o.Offset1D * 4 + 0] = color[3];   // Blue
                    pixels[o.Offset1D * 4 + 1] = color[2];     // Green
                    pixels[o.Offset1D * 4 + 2] = color[1];     // Red
                    pixels[o.Offset1D * 4 + 3] = color[0];   // Alpha
                });
        }
        private void DrawLines_Plate(int numSamples, double half, double lineThickness, AxisFor axisX, AxisFor axisY, AxisFor axisZ)
        {
            const double ELAPSEDURATIONSECONDS = 1;

            // Figure out how wide to make the plate
            int totalSamples = numSamples * numSamples * numSamples;        // numsamples is per axis, so cube it
            int cellsPerSlice = _field.Size * _field.Size;
            int numSlices = Convert.ToInt32(Math.Round(Convert.ToDouble(totalSamples) / Convert.ToDouble(cellsPerSlice)));
            if (numSlices == 0)
            {
                numSlices = 1;
            }

            int toOffset = numSlices / 2;
            int fromOffset = numSlices - toOffset - 1;

            DateTime now = DateTime.UtcNow;

            bool isOverField = false;

            if (_mousePoint != null)
            {
                #region Snap to mouse

                // Cast a ray (Copied this from ItemSelectDragLogic.ChangeDragPlane, DragItem)

                Point3D point = new Point3D(0, 0, 0);

                RayHitTestParameters cameraLookCenter = UtilityWPF.RayFromViewportPoint(_camera, _viewport, new Point(_viewport.ActualWidth * .5d, _viewport.ActualHeight * .5d));

                // Come up with a snap plane
                Vector3D standard = Math3D.GetArbitraryOrhonganal(cameraLookCenter.Direction);
                Vector3D orth = Vector3D.CrossProduct(standard, cameraLookCenter.Direction);
                ITriangle plane = new Triangle(point, point + standard, point + orth);

                DragHitShape dragPlane = new DragHitShape();
                dragPlane.SetShape_Plane(plane);

                // Cast a ray onto that plane from the current mouse position
                RayHitTestParameters mouseRay = UtilityWPF.RayFromViewportPoint(_camera, _viewport, _mousePoint.Value);
                Point3D? hitPoint = dragPlane.CastRay(mouseRay);

                if (hitPoint != null)
                {
                    // Find the nearest Z cell
                    double halfSize = (_field.Size * _sizeMult) / 2d;
                    double cellSize = (_field.Size * _sizeMult) / _field.Size;

                    int zIndex = Convert.ToInt32((halfSize - axisZ.GetValue(hitPoint.Value)) / cellSize);

                    if (zIndex >= 0 && zIndex < _field.Size)
                    {
                        isOverField = true;

                        // Cap to field
                        _plateCurrentIndex = _field.Size - zIndex;        // it's actually the opposite
                        if (_plateCurrentIndex - fromOffset < 0)
                        {
                            _plateCurrentIndex = fromOffset;
                        }
                        else if (_plateCurrentIndex + toOffset > _field.Size - 1)
                        {
                            _plateCurrentIndex = _field.Size - toOffset - 1;
                        }

                        _sceneRemaining = now + TimeSpan.FromSeconds(ELAPSEDURATIONSECONDS);
                    }
                }

                #endregion
            }

            if (!isOverField)
            {
                #region Shift the plate

                if (_plateCurrentIndex + toOffset > _field.Size - 1)
                {
                    _plateCurrentIndex = _field.Size - toOffset - 1;
                    _sceneRemaining = now + TimeSpan.FromSeconds(ELAPSEDURATIONSECONDS);
                }
                else if (now > _sceneRemaining)
                {
                    _plateCurrentIndex--;

                    if (_plateCurrentIndex - fromOffset <= 0)
                    {
                        _plateCurrentIndex = _field.Size - toOffset - 1;
                    }

                    _sceneRemaining = now + TimeSpan.FromSeconds(ELAPSEDURATIONSECONDS);
                }

                #endregion
            }

            double[] velX = _field.VelocityX;
            double[] velY = _field.VelocityY;
            double[] velZ = _field.VelocityZ;

            bool[] blocked = _field.Blocked;

            _velocityLines.BeginAddingLines();

            for (int z = _plateCurrentIndex - fromOffset; z <= _plateCurrentIndex + toOffset; z++)
            {
                for (int x = 0; x < _field.Size; x++)
                {
                    for (int y = 0; y < _field.Size; y++)
                    {
                        int xRef = -1;
                        int yRef = -1;
                        int zRef = -1;

                        axisX.Set3DIndex(ref xRef, ref yRef, ref zRef, x);
                        axisY.Set3DIndex(ref xRef, ref yRef, ref zRef, y);
                        axisZ.Set3DIndex(ref xRef, ref yRef, ref zRef, z);

                        int index1D = _field.Get1DIndex(xRef, yRef, zRef);

                        if (blocked[index1D])
                        {
                            continue;
                        }

                        DrawLinesSprtAddLine(xRef, yRef, zRef, index1D, half, lineThickness, velX, velY, velZ);
                    }
                }
            }

            _velocityLines.EndAddingLines();
        }