/// <summary> /// Shifts values (the new margins get zero) /// </summary> /// <param name="sweep">The other axis (if x's are getting shifted, then do it for each y)</param> /// <param name="shift">The row/column that has values changing. ex: [x] = [x+1]</param> private void TranslateValues(AxisFor sweep, AxisFor shift) { int amount; if (!int.TryParse(txtMoveAmount.Text, out amount)) { amount = 1; } amount *= shift.IsPos ? 1 : -1; foreach (int sweepIndex in sweep.Iterate()) { foreach (int shiftIndex in shift.Iterate()) { int toX = -1; int toY = -1; sweep.Set2DIndex(ref toX, ref toY, sweepIndex); shift.Set2DIndex(ref toX, ref toY, shiftIndex); int fromX = -1; int fromY = -1; sweep.Set2DIndex(ref fromX, ref fromY, sweepIndex); shift.Set2DIndex(ref fromX, ref fromY, shiftIndex + amount); double value = 0; if (fromX >= 0 && fromX < _width && fromY >= 0 && fromY < _height) { value = _values[fromY * _width + fromX]; } _values[toY * _width + toX] = value; } } RebuildBars(); PixelValueChanged(); }
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 static int GetOrthIncrement(AxisFor orth) { if (orth.Start == orth.Stop) { if (orth.Start == 0) { return 1; // it's sitting on the zero edge. Need to pull from the positive side } else { return -1; // likely sitting on the other edge } } else { return orth.Increment * -1; } }
public static Convolution2D ExtendBorders(Convolution2D conv, int width, int height) { if (width < conv.Width || height < conv.Height) { throw new ArgumentException(string.Format("The new size can't be smaller than old. Old={0},{1} -- New={2},{3}", conv.Width, conv.Height, width, height)); } VectorInt offset = new VectorInt() { X = (width - conv.Width) / 2, Y = (height - conv.Height) / 2, }; double[] values = new double[width * height]; #region Copy the image for (int y = 0; y < conv.Height; y++) { int offsetOrigY = y * conv.Width; int offsetNewY = (y + offset.Y) * width; for (int x = 0; x < conv.Width; x++) { values[offsetNewY + offset.X + x] = conv.Values[offsetOrigY + x]; } } #endregion #region Edges bool hasNegX = offset.X > 0; AxisFor forNegX = new AxisFor(Axis.X, offset.X - 1, 0); if (hasNegX) { ExtendEdge(values, width, conv.Width, forNegX, new AxisFor(Axis.Y, offset.Y, offset.Y + conv.Height - 1)); } bool hasNegY = offset.Y > 0; AxisFor forNegY = new AxisFor(Axis.Y, offset.Y - 1, 0); if (hasNegY) { ExtendEdge(values, width, conv.Height, forNegY, new AxisFor(Axis.X, offset.X, offset.X + conv.Width - 1)); } bool hasPosX = width > offset.X + conv.Width; AxisFor forPosX = new AxisFor(Axis.X, offset.X + conv.Width, width - 1); if (hasPosX) { ExtendEdge(values, width, conv.Width, forPosX, new AxisFor(Axis.Y, offset.Y, offset.Y + conv.Height - 1)); } bool hasPosY = height > offset.Y + conv.Height; AxisFor forPosY = new AxisFor(Axis.Y, offset.Y + conv.Height, height - 1); if (hasPosY) { ExtendEdge(values, width, conv.Height, forPosY, new AxisFor(Axis.X, offset.X, offset.X + conv.Width - 1)); } #endregion #region Corners if (hasNegX && hasNegY) { ExtendCorner(values, width, forNegX, forNegY); } if (hasPosX && hasNegY) { ExtendCorner(values, width, forPosX, forNegY); } if (hasPosX && hasPosY) { ExtendCorner(values, width, forPosX, forPosY); } if (hasNegX && hasPosY) { ExtendCorner(values, width, forNegX, forPosY); } #endregion return new Convolution2D(values, width, height, conv.IsNegPos); }
private static Convolution2D CopyRect(double[] values, int width, AxisFor edge, AxisFor orth, bool shouldRotate180) { if (shouldRotate180) { return CopyRect(values, width, new AxisFor(edge.Axis, edge.Stop, edge.Start), new AxisFor(orth.Axis, orth.Stop, orth.Start), false); } double[] retVal = new double[edge.Length * orth.Length]; int index = 0; foreach (int orthIndex in orth.Iterate()) { int x = -1; int y = -1; orth.Set2DIndex(ref x, ref y, orthIndex); foreach (int edgeIndex in edge.Iterate()) { edge.Set2DIndex(ref x, ref y, edgeIndex); retVal[index] = values[(y * width) + x]; index++; } } return new Convolution2D(retVal, edge.Length, orth.Length, false); }
private static void OverlayRow(double[] values, int width, int orthIndex, double[] overlay, AxisFor orth, AxisFor edge, /*double opacity,*/ bool isLeftToRight) { int x = -1; int y = -1; orth.Set2DIndex(ref x, ref y, orthIndex); for (int cntr = 0; cntr < overlay.Length; cntr++) { int edgeIndex = -1; if (isLeftToRight) { //edgeIndex = edge.Start + (cntr * edge.Increment); edgeIndex = edge.Start + ((cntr + 1) * edge.Increment); } else { //edgeIndex = edge.Stop - (cntr * edge.Increment); edgeIndex = edge.Stop - ((cntr + 1) * edge.Increment); } edge.Set2DIndex(ref x, ref y, edgeIndex); int index = (y * width) + x; //values[index] = UtilityCore.GetScaledValue(values[index], overlay[cntr], 0d, 1d, opacity); values[index] = overlay[cntr]; } }
/// <summary> /// Runs a gaussian over the current row and the two prior. Then copies those blurred values onto this row /// </summary> /// <param name="orthIndex">The row being copied to</param> private static void OverlayBlurredPixels(double[] values, int width, int orthHeight, int orthIndex, int orthInc, int edgeMidStart, int edgeMidStop, AxisFor orth, AxisFor edge, Convolution2D gauss, /*int opacityDistance,*/ Random rand) { if (orthHeight < 2 || edgeMidStop < edgeMidStart) { // There's not enough to do a full 3x3 blur. A smaller sized blur could be done, but that's a lot of extra logic, and not really worth the trouble return; } //double opacity = UtilityCore.GetScaledValue_Capped(0d, 1d, 0, opacityDistance, Math.Abs(orthIndex - orth.Start)); // Get a random midpoint int edgeMid = rand.Next(edgeMidStart, edgeMidStop + 1); AxisFor orth3 = new AxisFor(orth.Axis, orthIndex + (orthInc * 2), orthIndex); AxisFor leftEdge = new AxisFor(edge.Axis, edge.Start, edgeMid + (edge.Increment * 2)); AxisFor rightEdge = new AxisFor(edge.Axis, edgeMid - (edge.Increment * 2), edge.Stop); // Copy the values (these are 3 tall, and edgeMid-edgeStart+2 wide) Convolution2D leftRect = CopyRect(values, width, leftEdge, orth3, false); Convolution2D rightRect = CopyRect(values, width, rightEdge, orth3, true); // this one is rotated 180 so that when the gaussian is applied, it will be from the right border to the mid // Apply a gaussian (these are 1 tall, and edgeMid-edgeStart wide) Convolution2D leftBlurred = Convolutions.Convolute(leftRect, gauss); Convolution2D rightBlurred = Convolutions.Convolute(rightRect, gauss); // Overlay onto this newest row //TODO: use an opacity LERP from original edge OverlayRow(values, width, orthIndex, leftBlurred.Values, orth, edge, /*opacity,*/ true); OverlayRow(values, width, orthIndex, rightBlurred.Values, orth, edge, /*opacity,*/ false); }
/// <summary> /// For each pixel in this row, pick a random pixel from a box above /// </summary> /// <remarks> /// At each pixel, this draws from a box of size orthDepth x (edgeDepth*2)+1 /// </remarks> /// <param name="orthIndex">The row being copied to</param> private static void CopyRandomPixels(double[] values, int width, int orthIndex, int orthInc, int orthDepth, int edgeDepth, AxisFor orth, AxisFor edge, Random rand) { // See how many rows to randomly pull from int orthDepthStart = orthIndex + orthInc; int orthDepthStop = orthIndex + (orthDepth * orthInc); UtilityCore.MinMax(ref orthDepthStart, ref orthDepthStop); int orthAdd = orthInc < 0 ? 1 : 0; foreach (int edgeIndex in edge.Iterate()) { // Figure out which column to pull from int toEdge = -1; do { toEdge = rand.Next(edgeIndex - edgeDepth, edgeIndex + edgeDepth + 1); } while (!edge.IsBetween(toEdge)); // Figure out which row to pull from int toOrth = rand.Next(orthDepthStart, orthDepthStop) + orthAdd; // From XY int fromX = -1; int fromY = -1; orth.Set2DIndex(ref fromX, ref fromY, toOrth); edge.Set2DIndex(ref fromX, ref fromY, toEdge); // To XY int toX = -1; int toY = -1; orth.Set2DIndex(ref toX, ref toY, orthIndex); edge.Set2DIndex(ref toX, ref toY, edgeIndex); // Copy pixel values[(toY * width) + toX] = values[(fromY * width) + fromX]; } }
//TODO: Implement this properly private static void ExtendCorner(double[] values, int width, AxisFor orth, AxisFor edge) { Random rand = StaticRandom.GetRandomForThread(); foreach (int edgeIndex in edge.Iterate()) { foreach (int orthIndex in orth.Iterate()) { int x = -1; int y = -1; orth.Set2DIndex(ref x, ref y, orthIndex); edge.Set2DIndex(ref x, ref y, edgeIndex); values[(y * width) + x] = rand.Next(256); } } }
private static void ExtendEdge(double[] values, int width, int orthHeight, AxisFor orth, AxisFor edge) { const int EDGEDEPTH = 4; int ORTHDEPTH = Math.Min(5, orthHeight) + 1; const int EDGEMIDOFFSET = 5; // This isn't worth implementing, you just get a pixelated band //const int GAUSSOPACITYDIST = 1; // how many pixels before the gaussian is full strength Random rand = StaticRandom.GetRandomForThread(); Convolution2D gauss = Convolutions.GetGaussian(3); // Figure out which direction to copy rows from int orthInc = GetOrthIncrement(orth); #region Edge midpoint range // Each row, a random midpoint is chosen. This way, an artifact won't be created down the middle. // mid start and stop are the range of possible values that the random midpoint can be from int edgeMidStart = edge.Start; int edgeMidStop = edge.Stop; UtilityCore.MinMax(ref edgeMidStart, ref edgeMidStop); edgeMidStart += EDGEMIDOFFSET; edgeMidStop -= EDGEMIDOFFSET; #endregion foreach (int orthIndex in orth.Iterate()) { CopyRandomPixels(values, width, orthIndex, orthInc, ORTHDEPTH, EDGEDEPTH, orth, edge, rand); OverlayBlurredPixels(values, width, orthHeight, orthIndex, orthInc, edgeMidStart, edgeMidStop, orth, edge, gauss, /*GAUSSOPACITYDIST,*/ rand); } }
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(); }