Revert "refactor(engine): simplify FillExtents logic using Compactor.Push"
This reverts commit d1d47b5223.
This commit is contained in:
@@ -3,7 +3,6 @@ using OpenNest.Math;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace OpenNest.Engine.Fill
|
namespace OpenNest.Engine.Fill
|
||||||
@@ -14,11 +13,13 @@ namespace OpenNest.Engine.Fill
|
|||||||
|
|
||||||
private readonly Box workArea;
|
private readonly Box workArea;
|
||||||
private readonly double partSpacing;
|
private readonly double partSpacing;
|
||||||
|
private readonly double halfSpacing;
|
||||||
|
|
||||||
public FillExtents(Box workArea, double partSpacing)
|
public FillExtents(Box workArea, double partSpacing)
|
||||||
{
|
{
|
||||||
this.workArea = workArea;
|
this.workArea = workArea;
|
||||||
this.partSpacing = partSpacing;
|
this.partSpacing = partSpacing;
|
||||||
|
halfSpacing = partSpacing / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Part> Fill(Drawing drawing, double rotationAngle = 0,
|
public List<Part> Fill(Drawing drawing, double rotationAngle = 0,
|
||||||
@@ -26,18 +27,18 @@ namespace OpenNest.Engine.Fill
|
|||||||
CancellationToken token = default,
|
CancellationToken token = default,
|
||||||
IProgress<NestProgress> progress = null)
|
IProgress<NestProgress> progress = null)
|
||||||
{
|
{
|
||||||
var initialPair = CreatePair(drawing, rotationAngle, rotationAngle + System.Math.PI);
|
var pair = BuildPair(drawing, rotationAngle);
|
||||||
if (initialPair == null)
|
if (pair == null)
|
||||||
return new List<Part>();
|
return new List<Part>();
|
||||||
|
|
||||||
var column = BuildColumn(initialPair.Value.part1, initialPair.Value.part2, initialPair.Value.pairBbox);
|
var column = BuildColumn(pair.Value.part1, pair.Value.part2, pair.Value.pairBbox);
|
||||||
if (column.Count == 0)
|
if (column.Count == 0)
|
||||||
return new List<Part>();
|
return new List<Part>();
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
||||||
column, workArea, $"Extents: initial column {column.Count} parts");
|
column, workArea, $"Extents: initial column {column.Count} parts");
|
||||||
|
|
||||||
var adjusted = AdjustColumn(initialPair.Value, column, token);
|
var adjusted = AdjustColumn(pair.Value, column, token);
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
||||||
adjusted, workArea, $"Extents: adjusted column {adjusted.Count} parts");
|
adjusted, workArea, $"Extents: adjusted column {adjusted.Count} parts");
|
||||||
@@ -52,33 +53,52 @@ namespace OpenNest.Engine.Fill
|
|||||||
|
|
||||||
// --- Step 1: Pair Construction ---
|
// --- Step 1: Pair Construction ---
|
||||||
|
|
||||||
private (Part part1, Part part2, Box pairBbox)? CreatePair(
|
private (Part part1, Part part2, Box pairBbox)? BuildPair(Drawing drawing, double rotationAngle)
|
||||||
Drawing drawing, double rotation1, double rotation2, double verticalShift2 = 0)
|
|
||||||
{
|
{
|
||||||
var p1 = Part.CreateAtOrigin(drawing, rotation1);
|
var part1 = Part.CreateAtOrigin(drawing, rotationAngle);
|
||||||
var p2 = Part.CreateAtOrigin(drawing, rotation2);
|
var part2 = Part.CreateAtOrigin(drawing, rotationAngle + System.Math.PI);
|
||||||
|
|
||||||
// Initial positioning: p2 to the right of p1, with optional vertical shift.
|
// Check that each part fits in the work area individually.
|
||||||
p2.Offset(p1.BoundingBox.Width + partSpacing, verticalShift2);
|
if (part1.BoundingBox.Width > workArea.Width + Tolerance.Epsilon ||
|
||||||
|
part1.BoundingBox.Length > workArea.Length + Tolerance.Epsilon)
|
||||||
|
return null;
|
||||||
|
|
||||||
// Compact p2 left toward p1 using geometry-aware distance.
|
// Slide part2 toward part1 from the right using geometry-aware distance.
|
||||||
Compactor.Push(new List<Part> { p2 }, new List<Part> { p1 }, workArea, partSpacing, PushDirection.Left);
|
var boundary1 = new PartBoundary(part1, halfSpacing);
|
||||||
|
var boundary2 = new PartBoundary(part2, halfSpacing);
|
||||||
|
|
||||||
var pairBbox = ((IEnumerable<IBoundable>)new IBoundable[] { p1, p2 }).GetBoundingBox();
|
// Position part2 to the right of part1 at bounding box width distance.
|
||||||
|
var startOffset = part1.BoundingBox.Width + part2.BoundingBox.Width + partSpacing;
|
||||||
|
part2.Offset(startOffset, 0);
|
||||||
|
part2.UpdateBounds();
|
||||||
|
|
||||||
// Re-anchor pair to work area origin (bottom-left).
|
// Slide part2 left toward part1.
|
||||||
|
var movingLines = boundary2.GetLines(part2.Location, PushDirection.Left);
|
||||||
|
var stationaryLines = boundary1.GetLines(part1.Location, PushDirection.Right);
|
||||||
|
var dist = SpatialQuery.DirectionalDistance(movingLines, stationaryLines, PushDirection.Left);
|
||||||
|
|
||||||
|
if (dist < double.MaxValue && dist > 0)
|
||||||
|
{
|
||||||
|
part2.Offset(-dist, 0);
|
||||||
|
part2.UpdateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-anchor pair to work area origin.
|
||||||
|
var pairBbox = ((IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }).GetBoundingBox();
|
||||||
var anchor = new Vector(workArea.X - pairBbox.Left, workArea.Y - pairBbox.Bottom);
|
var anchor = new Vector(workArea.X - pairBbox.Left, workArea.Y - pairBbox.Bottom);
|
||||||
p1.Offset(anchor);
|
part1.Offset(anchor);
|
||||||
p2.Offset(anchor);
|
part2.Offset(anchor);
|
||||||
|
part1.UpdateBounds();
|
||||||
|
part2.UpdateBounds();
|
||||||
|
|
||||||
pairBbox = ((IEnumerable<IBoundable>)new IBoundable[] { p1, p2 }).GetBoundingBox();
|
pairBbox = ((IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }).GetBoundingBox();
|
||||||
|
|
||||||
// Verify pair fits in work area.
|
// Verify pair fits in work area.
|
||||||
if (pairBbox.Width > workArea.Width + Tolerance.Epsilon ||
|
if (pairBbox.Width > workArea.Width + Tolerance.Epsilon ||
|
||||||
pairBbox.Length > workArea.Length + Tolerance.Epsilon)
|
pairBbox.Length > workArea.Length + Tolerance.Epsilon)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (p1, p2, pairBbox);
|
return (part1, part2, pairBbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Step 2: Build Column (tile vertically) ---
|
// --- Step 2: Build Column (tile vertically) ---
|
||||||
@@ -87,104 +107,193 @@ namespace OpenNest.Engine.Fill
|
|||||||
{
|
{
|
||||||
var column = new List<Part> { (Part)part1.Clone(), (Part)part2.Clone() };
|
var column = new List<Part> { (Part)part1.Clone(), (Part)part2.Clone() };
|
||||||
|
|
||||||
var copyDistance = ComputeVerticalCopyDistance(part1, part2, pairBbox);
|
// Find geometry-aware copy distance for the pair vertically.
|
||||||
|
var boundary1 = new PartBoundary(part1, halfSpacing);
|
||||||
|
var boundary2 = new PartBoundary(part2, halfSpacing);
|
||||||
|
|
||||||
|
// Compute vertical copy distance using bounding boxes as starting point,
|
||||||
|
// then slide down to find true geometry distance.
|
||||||
|
var pairHeight = pairBbox.Length;
|
||||||
|
var testOffset = new Vector(0, pairHeight);
|
||||||
|
|
||||||
|
// Create test parts for slide distance measurement.
|
||||||
|
var testPart1 = part1.CloneAtOffset(testOffset);
|
||||||
|
var testPart2 = part2.CloneAtOffset(testOffset);
|
||||||
|
|
||||||
|
// Find minimum distance from test pair sliding down toward original pair.
|
||||||
|
var copyDistance = FindVerticalCopyDistance(
|
||||||
|
part1, part2, testPart1, testPart2,
|
||||||
|
boundary1, boundary2, pairHeight);
|
||||||
|
|
||||||
if (copyDistance <= 0)
|
if (copyDistance <= 0)
|
||||||
return column;
|
return column;
|
||||||
|
|
||||||
var pairHeight = pairBbox.Length;
|
var count = 1;
|
||||||
var currentY = pairBbox.Bottom + copyDistance;
|
while (true)
|
||||||
|
|
||||||
while (currentY + pairHeight <= workArea.Top + Tolerance.Epsilon)
|
|
||||||
{
|
{
|
||||||
var offset = new Vector(0, currentY - pairBbox.Bottom);
|
var nextBottom = pairBbox.Bottom + copyDistance * count;
|
||||||
|
if (nextBottom + pairHeight > workArea.Top + Tolerance.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var offset = new Vector(0, copyDistance * count);
|
||||||
column.Add(part1.CloneAtOffset(offset));
|
column.Add(part1.CloneAtOffset(offset));
|
||||||
column.Add(part2.CloneAtOffset(offset));
|
column.Add(part2.CloneAtOffset(offset));
|
||||||
currentY += copyDistance;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double ComputeVerticalCopyDistance(Part p1, Part p2, Box pairBbox)
|
private double FindVerticalCopyDistance(
|
||||||
|
Part origPart1, Part origPart2,
|
||||||
|
Part testPart1, Part testPart2,
|
||||||
|
PartBoundary boundary1, PartBoundary boundary2,
|
||||||
|
double pairHeight)
|
||||||
{
|
{
|
||||||
var pairHeight = pairBbox.Length;
|
// Check all 4 combinations: test parts sliding down toward original parts.
|
||||||
// Start the test pair high enough so it doesn't overlap the original pair's bounding box initially.
|
var minSlide = double.MaxValue;
|
||||||
var startOffset = pairHeight + partSpacing;
|
|
||||||
var testParts = new List<Part> { p1.CloneAtOffset(new Vector(0, startOffset)), p2.CloneAtOffset(new Vector(0, startOffset)) };
|
|
||||||
var obstacles = new List<Part> { p1, p2 };
|
|
||||||
|
|
||||||
// Use a large work area to prevent edge-clamping during distance measurement.
|
// Test1 -> Orig1
|
||||||
var largeWorkArea = new Box(workArea.X, workArea.Y - pairHeight, workArea.Width, workArea.Length + pairHeight * 3);
|
var d = SlideDistance(boundary1, testPart1.Location, boundary1, origPart1.Location, PushDirection.Down);
|
||||||
var slide = Compactor.Push(testParts, obstacles, largeWorkArea, partSpacing, PushDirection.Down);
|
if (d < minSlide) minSlide = d;
|
||||||
|
|
||||||
|
// Test1 -> Orig2
|
||||||
|
d = SlideDistance(boundary1, testPart1.Location, boundary2, origPart2.Location, PushDirection.Down);
|
||||||
|
if (d < minSlide) minSlide = d;
|
||||||
|
|
||||||
|
// Test2 -> Orig1
|
||||||
|
d = SlideDistance(boundary2, testPart2.Location, boundary1, origPart1.Location, PushDirection.Down);
|
||||||
|
if (d < minSlide) minSlide = d;
|
||||||
|
|
||||||
|
// Test2 -> Orig2
|
||||||
|
d = SlideDistance(boundary2, testPart2.Location, boundary2, origPart2.Location, PushDirection.Down);
|
||||||
|
if (d < minSlide) minSlide = d;
|
||||||
|
|
||||||
|
if (minSlide >= double.MaxValue || minSlide < 0)
|
||||||
|
return pairHeight + partSpacing;
|
||||||
|
|
||||||
|
// Match FillLinear.ComputeCopyDistance: copyDist = startOffset - slide,
|
||||||
|
// clamped so it never goes below pairHeight + partSpacing to prevent
|
||||||
|
// bounding-box overlap from spurious slide values.
|
||||||
|
var copyDist = pairHeight - minSlide;
|
||||||
|
|
||||||
// True copy distance = start - slide. Clamp to BB height + spacing to prevent BB overlap.
|
|
||||||
var copyDist = startOffset - slide;
|
|
||||||
return System.Math.Max(copyDist, pairHeight + partSpacing);
|
return System.Math.Max(copyDist, pairHeight + partSpacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double SlideDistance(
|
||||||
|
PartBoundary movingBoundary, Vector movingLocation,
|
||||||
|
PartBoundary stationaryBoundary, Vector stationaryLocation,
|
||||||
|
PushDirection direction)
|
||||||
|
{
|
||||||
|
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||||
|
var movingEdges = movingBoundary.GetEdges(direction);
|
||||||
|
var stationaryEdges = stationaryBoundary.GetEdges(opposite);
|
||||||
|
|
||||||
|
return SpatialQuery.DirectionalDistance(
|
||||||
|
movingEdges, movingLocation,
|
||||||
|
stationaryEdges, stationaryLocation,
|
||||||
|
direction);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Step 3: Iterative Adjustment ---
|
// --- Step 3: Iterative Adjustment ---
|
||||||
|
|
||||||
private List<Part> AdjustColumn(
|
private List<Part> AdjustColumn(
|
||||||
(Part part1, Part part2, Box pairBbox) initialPair,
|
(Part part1, Part part2, Box pairBbox) pair,
|
||||||
List<Part> initialColumn,
|
List<Part> column,
|
||||||
CancellationToken token)
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
var currentPair = initialPair;
|
var originalPairWidth = pair.pairBbox.Width;
|
||||||
var currentColumn = initialColumn;
|
|
||||||
var originalWidth = initialPair.pairBbox.Width;
|
|
||||||
|
|
||||||
for (var iteration = 0; iteration < MaxIterations; iteration++)
|
for (var iteration = 0; iteration < MaxIterations; iteration++)
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var columnBbox = ((IEnumerable<IBoundable>)currentColumn).GetBoundingBox();
|
// Measure current gap.
|
||||||
var gap = workArea.Top - columnBbox.Top;
|
var topEdge = double.MinValue;
|
||||||
|
foreach (var p in column)
|
||||||
|
if (p.BoundingBox.Top > topEdge)
|
||||||
|
topEdge = p.BoundingBox.Top;
|
||||||
|
|
||||||
|
var gap = workArea.Top - topEdge;
|
||||||
|
|
||||||
if (gap <= Tolerance.Epsilon)
|
if (gap <= Tolerance.Epsilon)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var pairCount = currentColumn.Count / 2;
|
var pairCount = column.Count / 2;
|
||||||
|
if (pairCount <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
var adjustment = gap / pairCount;
|
var adjustment = gap / pairCount;
|
||||||
if (adjustment <= Tolerance.Epsilon)
|
if (adjustment <= Tolerance.Epsilon)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Try shifting p2 up or down relative to p1 to see if we can close the gap
|
// Try adjusting the pair and rebuilding the column.
|
||||||
// without making the pair wider than its initial horizontal footprint.
|
var adjusted = TryAdjustPair(pair, adjustment, originalPairWidth);
|
||||||
var adjusted = TryAdjustPair(currentPair, adjustment, originalWidth);
|
|
||||||
if (adjusted == null)
|
if (adjusted == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var newColumn = BuildColumn(adjusted.Value.part1, adjusted.Value.part2, adjusted.Value.pairBbox);
|
var newColumn = BuildColumn(adjusted.Value.part1, adjusted.Value.part2, adjusted.Value.pairBbox);
|
||||||
if (newColumn.Count <= currentColumn.Count)
|
if (newColumn.Count == 0)
|
||||||
break; // No improvement in part count.
|
break;
|
||||||
|
|
||||||
currentColumn = newColumn;
|
column = newColumn;
|
||||||
currentPair = adjusted.Value;
|
pair = adjusted.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentColumn;
|
return column;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Part part1, Part part2, Box pairBbox)? TryAdjustPair(
|
private (Part part1, Part part2, Box pairBbox)? TryAdjustPair(
|
||||||
(Part part1, Part part2, Box pairBbox) pair,
|
(Part part1, Part part2, Box pairBbox) pair,
|
||||||
double adjustment, double maxWidth)
|
double adjustment, double originalPairWidth)
|
||||||
{
|
{
|
||||||
// Try shifting part2 up first.
|
// Try shifting part2 up first.
|
||||||
var result = CreatePair(pair.part1.BaseDrawing, pair.part1.Rotation, pair.part2.Rotation,
|
var result = TryShiftDirection(pair, adjustment, originalPairWidth);
|
||||||
(pair.part2.Location.Y - pair.part1.Location.Y) + adjustment);
|
|
||||||
|
|
||||||
if (result != null && result.Value.pairBbox.Width <= maxWidth + Tolerance.Epsilon)
|
if (result != null)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// Up made it wider or didn't fit — try down instead.
|
// Up made the pair wider — try down instead.
|
||||||
result = CreatePair(pair.part1.BaseDrawing, pair.part1.Rotation, pair.part2.Rotation,
|
return TryShiftDirection(pair, -adjustment, originalPairWidth);
|
||||||
(pair.part2.Location.Y - pair.part1.Location.Y) - adjustment);
|
}
|
||||||
|
|
||||||
if (result != null && result.Value.pairBbox.Width <= maxWidth + Tolerance.Epsilon)
|
private (Part part1, Part part2, Box pairBbox)? TryShiftDirection(
|
||||||
return result;
|
(Part part1, Part part2, Box pairBbox) pair,
|
||||||
|
double verticalShift, double originalPairWidth)
|
||||||
|
{
|
||||||
|
// Clone parts so we don't mutate the originals.
|
||||||
|
var p1 = (Part)pair.part1.Clone();
|
||||||
|
var p2 = (Part)pair.part2.Clone();
|
||||||
|
|
||||||
return null;
|
// Separate: shift part2 right so bounding boxes don't touch.
|
||||||
|
p2.Offset(partSpacing, 0);
|
||||||
|
p2.UpdateBounds();
|
||||||
|
|
||||||
|
// Apply the vertical shift.
|
||||||
|
p2.Offset(0, verticalShift);
|
||||||
|
p2.UpdateBounds();
|
||||||
|
|
||||||
|
// Compact part2 left toward part1.
|
||||||
|
var moving = new List<Part> { p2 };
|
||||||
|
var obstacles = new List<Part> { p1 };
|
||||||
|
Compactor.Push(moving, obstacles, workArea, partSpacing, PushDirection.Left);
|
||||||
|
|
||||||
|
// Check if the pair got wider.
|
||||||
|
var newBbox = ((IEnumerable<IBoundable>)new IBoundable[] { p1, p2 }).GetBoundingBox();
|
||||||
|
|
||||||
|
if (newBbox.Width > originalPairWidth + Tolerance.Epsilon)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Re-anchor to work area origin.
|
||||||
|
var anchor = new Vector(workArea.X - newBbox.Left, workArea.Y - newBbox.Bottom);
|
||||||
|
p1.Offset(anchor);
|
||||||
|
p2.Offset(anchor);
|
||||||
|
p1.UpdateBounds();
|
||||||
|
p2.UpdateBounds();
|
||||||
|
|
||||||
|
newBbox = ((IEnumerable<IBoundable>)new IBoundable[] { p1, p2 }).GetBoundingBox();
|
||||||
|
return (p1, p2, newBbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Step 4: Horizontal Repetition ---
|
// --- Step 4: Horizontal Repetition ---
|
||||||
@@ -197,21 +306,36 @@ namespace OpenNest.Engine.Fill
|
|||||||
var columnBbox = ((IEnumerable<IBoundable>)column).GetBoundingBox();
|
var columnBbox = ((IEnumerable<IBoundable>)column).GetBoundingBox();
|
||||||
var columnWidth = columnBbox.Width;
|
var columnWidth = columnBbox.Width;
|
||||||
|
|
||||||
// Create a test column shifted right and compact it left to find the true copy distance.
|
// Create a test column shifted right by columnWidth + spacing.
|
||||||
var startOffset = columnWidth + partSpacing;
|
var testOffset = columnWidth + partSpacing;
|
||||||
var testColumn = column.Select(p => p.CloneAtOffset(new Vector(startOffset, 0))).ToList();
|
var testColumn = new List<Part>(column.Count);
|
||||||
|
foreach (var part in column)
|
||||||
|
testColumn.Add(part.CloneAtOffset(new Vector(testOffset, 0)));
|
||||||
|
|
||||||
var slide = Compactor.Push(testColumn, column, workArea, partSpacing, PushDirection.Left);
|
// Compact the test column left against the original column.
|
||||||
var copyDistance = startOffset - slide;
|
var distanceMoved = Compactor.Push(testColumn, column, workArea, partSpacing, PushDirection.Left);
|
||||||
|
|
||||||
|
// Derive the true copy distance from where the test column ended up.
|
||||||
|
var testBbox = ((IEnumerable<IBoundable>)testColumn).GetBoundingBox();
|
||||||
|
var copyDistance = testBbox.Left - columnBbox.Left;
|
||||||
|
|
||||||
if (copyDistance <= Tolerance.Epsilon)
|
if (copyDistance <= Tolerance.Epsilon)
|
||||||
copyDistance = columnWidth + partSpacing;
|
copyDistance = columnWidth + partSpacing;
|
||||||
|
|
||||||
Debug.WriteLine($"[FillExtents] Column copy distance: {copyDistance:F2} (bbox width: {columnWidth:F2}, spacing: {partSpacing:F2})");
|
Debug.WriteLine($"[FillExtents] Column copy distance: {copyDistance:F2} (bbox width: {columnWidth:F2}, spacing: {partSpacing:F2})");
|
||||||
|
|
||||||
|
// Build all columns.
|
||||||
var result = new List<Part>(column);
|
var result = new List<Part>(column);
|
||||||
var colIndex = 1;
|
|
||||||
|
|
||||||
|
// Add the test column we already computed as column 2.
|
||||||
|
foreach (var part in testColumn)
|
||||||
|
{
|
||||||
|
if (IsWithinWorkArea(part))
|
||||||
|
result.Add(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile additional columns at the copy distance.
|
||||||
|
var colIndex = 2;
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var offset = new Vector(copyDistance * colIndex, 0);
|
var offset = new Vector(copyDistance * colIndex, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user