Skip to content

Commit

Permalink
Slots from geometry fix & Solver time limiter (#42)
Browse files Browse the repository at this point in the history
Co-authored-by: janper <[email protected]>
  • Loading branch information
janper and janper authored May 4, 2021
1 parent 91f0695 commit 05f73a3
Show file tree
Hide file tree
Showing 6 changed files with 1,186 additions and 95 deletions.
137 changes: 49 additions & 88 deletions Components/SlotsFromGeometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Geometry;

namespace Monoceros {
Expand Down Expand Up @@ -68,6 +67,7 @@ protected override void SolveInstance(IGH_DataAccess DA) {
return;
}

// Transform the geometry to be oriented to world XYZ fore easier scanning
var geometryTransform = Transform.PlaneToPlane(basePlane, Plane.WorldXY);
var geometryClean = geometryRaw
.Select(goo => GH_Convert.ToGeometryBase(goo))
Expand All @@ -83,140 +83,101 @@ protected override void SolveInstance(IGH_DataAccess DA) {
return;
}

var moduleTransform = Transform.PlaneToPlane(module.BasePlane, Plane.WorldXY);
var moduleGeometry = module.Geometry
.Concat(module.ReferencedGeometry)
.Select(geo => {
var transformedGeometry = geo.Duplicate();
transformedGeometry.Transform(moduleTransform);
return transformedGeometry;
});
var modulePartCenters = module.PartCenters
.Select(point => {
var transformedPoint = point.ToCartesian(module.BasePlane, module.PartDiagonal);
transformedPoint.Transform(moduleTransform);
return transformedPoint;
});
.Concat(module.ReferencedGeometry);
if (!moduleGeometry.Any()) {
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning,
"Module \"" + module.Name + "\" contains " +
"no geometry and therefore will be skipped.");
return;
}

var moduleBBoxes = moduleGeometry.Select(geo => geo.GetBoundingBox(false));
var modulePivot = module.Pivot;
modulePivot.Transform(moduleTransform);
var modulePivotOrigin = modulePivot.Origin;
var moduleGeometryBBoxes = moduleGeometry.Select(geo => geo.GetBoundingBox(true));

var commonModuleBBox = BoundingBox.Empty;
commonModuleBBox.Union(modulePivotOrigin);

foreach (var moduleBBox in moduleBBoxes) {
commonModuleBBox.Union(moduleBBox);
var bBoxUnionModule = BoundingBox.Empty;
bBoxUnionModule.Union(module.Pivot.Origin);
foreach (var moduleBBox in moduleGeometryBBoxes) {
bBoxUnionModule.Union(moduleBBox);
}
var moduleDimensionSafetyBuffer = new Point3i(
(int)Math.Ceiling(bBoxUnionModule.Diagonal.X / module.PartDiagonal.X) + 1,
(int)Math.Ceiling(bBoxUnionModule.Diagonal.Y / module.PartDiagonal.Y) + 1,
(int)Math.Ceiling(bBoxUnionModule.Diagonal.Z / module.PartDiagonal.Z) + 1);

var safetyBuffer = new Point3i(
(int)Math.Ceiling(commonModuleBBox.Diagonal.X / module.PartDiagonal.X) + 1,
(int)Math.Ceiling(commonModuleBBox.Diagonal.Y / module.PartDiagonal.Y) + 1,
(int)Math.Ceiling(commonModuleBBox.Diagonal.Z / module.PartDiagonal.Z) + 1);

var geometryBBoxes = geometryClean.Select(geo => geo.GetBoundingBox(false)).ToList();
var commonBBox = BoundingBox.Empty;

var geometryBBoxes = geometryClean.Select(geo => geo.GetBoundingBox(true)).ToList();
var bBoxUnionGeometry = BoundingBox.Empty;
foreach (var bBox in geometryBBoxes) {
commonBBox.Union(bBox);
bBoxUnionGeometry.Union(bBox);
}

var slots = new List<Slot>();

for (int z = (int)Math.Floor(commonBBox.Min.Z / module.PartDiagonal.Z) - safetyBuffer.Z;
z < Math.Ceiling(commonBBox.Max.Z / module.PartDiagonal.Z) + safetyBuffer.Z;
for (int z = (int)Math.Floor(bBoxUnionGeometry.Min.Z / module.PartDiagonal.Z) - moduleDimensionSafetyBuffer.Z;
z < Math.Ceiling(bBoxUnionGeometry.Max.Z / module.PartDiagonal.Z) + moduleDimensionSafetyBuffer.Z;
z++) {
for (int y = (int)Math.Floor(commonBBox.Min.Y / module.PartDiagonal.Y) - safetyBuffer.Y;
y < Math.Ceiling(commonBBox.Max.Y / module.PartDiagonal.Y) + safetyBuffer.Y;
for (int y = (int)Math.Floor(bBoxUnionGeometry.Min.Y / module.PartDiagonal.Y) - moduleDimensionSafetyBuffer.Y;
y < Math.Ceiling(bBoxUnionGeometry.Max.Y / module.PartDiagonal.Y) + moduleDimensionSafetyBuffer.Y;
y++) {
for (int x = (int)Math.Floor(commonBBox.Min.X / module.PartDiagonal.X) - safetyBuffer.X;
x < Math.Ceiling(commonBBox.Max.X / module.PartDiagonal.X) + safetyBuffer.X;
for (int x = (int)Math.Floor(bBoxUnionGeometry.Min.X / module.PartDiagonal.X) - moduleDimensionSafetyBuffer.X;
x < Math.Ceiling(bBoxUnionGeometry.Max.X / module.PartDiagonal.X) + moduleDimensionSafetyBuffer.X;
x++) {
var currentRelativePosition = new Point3i(x, y, z);
var currentPivot = Plane.WorldXY;
var currentPivotOrigin = new Point3d(x * module.PartDiagonal.X,
y * module.PartDiagonal.Y,
z * module.PartDiagonal.Z);
currentPivot.Origin = currentPivotOrigin;
var currentTransform = Transform.PlaneToPlane(modulePivot, currentPivot);
var transformedModuleBBoxes = moduleBBoxes.Select(bBox => {
currentPivot.Origin = currentRelativePosition.ToCartesian(Plane.WorldXY, module.PartDiagonal);

var transformModuleToCurrentPivot = Transform.PlaneToPlane(module.Pivot, currentPivot);
var moduleGeometryBBoxesAtCurrentPivot = moduleGeometryBBoxes.Select(bBox => {
var transformedBBox = bBox;
transformedBBox.Transform(currentTransform);
transformedBBox.Transform(transformModuleToCurrentPivot);
return transformedBBox;
});

var indicesOfSimilarGeometries = new List<int>();
var allModuleBoxesFoundSimilarGeometryBox = true;
foreach (var currentBBox in transformedModuleBBoxes) {
var foundBoxForCurrent = false;
var otherIndex = 0;
foreach (var otherBBox in geometryBBoxes) {
var currentPoints = currentBBox.GetCorners();
var otherPoints = otherBBox.GetCorners();
for (var i = 0; i < currentPoints.Length; i++) {
var currentPoint = currentPoints[i];
var otherPoint = otherPoints[i];
var equalPoints = currentPoint.EpsilonEquals(otherPoint, RhinoMath.SqrtEpsilon);
if (!equalPoints) {
break;
}
foundBoxForCurrent = true;
}
if (foundBoxForCurrent) {
indicesOfSimilarGeometries.Add(otherIndex);
break;
var indicesOfSimilarBBoxes = moduleGeometryBBoxesAtCurrentPivot.Select(moduleGeometryBBox =>
geometryBBoxes.Select((geometryBBox, index) => {
var moduleCorners = moduleGeometryBBox.GetCorners().ToList();
var geometryCorners = geometryBBox.GetCorners().ToList();
moduleCorners.Sort();
geometryCorners.Sort();
if (Enumerable.SequenceEqual(moduleCorners, geometryCorners)) {
return index;
} else {
return -1;
}
otherIndex++;
}
if (!foundBoxForCurrent) {
allModuleBoxesFoundSimilarGeometryBox = false;
break;
}
}
}).Where(index => index != -1)
);

if (!allModuleBoxesFoundSimilarGeometryBox) {
// If any module geometry doesn't have a bbox similar to any geometry, then early continue
if (!indicesOfSimilarBBoxes.All(similarBBoxes => similarBBoxes.Any())) {
continue;
}

// Heavy calculations

var transformedModuleGeometry = moduleGeometry.Select(geo => {
var transformedGeometry = geo.Duplicate();
transformedGeometry.Transform(currentTransform);
transformedGeometry.Transform(transformModuleToCurrentPivot);
return transformedGeometry;
});

var geometriesToCheck = indicesOfSimilarGeometries.Select(index => geometryClean[index]);
var geometriesToCheck = indicesOfSimilarBBoxes.Select(indices => indices.Select(index => geometryClean[index]));

var geometryEqualityPattern = transformedModuleGeometry
.Zip(geometriesToCheck, (current, other) => GeometryBase.GeometryEquals(current, other));
.Zip(geometriesToCheck, (current, others) => others.Any(other =>
// TODO: when the original geometry is moved, the meshes become unequal
// TODO: replace with visual similarity check (pull random points to geometry)
// TODO: check if the two are of the same type first
GeometryBase.GeometryEquals(current, other)
));

if (geometryEqualityPattern.All(equal => equal)) {

var transformedModulePartCenters = modulePartCenters.Select(centerPoint => {
var transformedPoint = centerPoint;
transformedPoint.Transform(currentTransform);
return transformedPoint;
});

var currentPivotCenter = new Point3d(currentPivotOrigin.X / module.PartDiagonal.X,
currentPivotOrigin.Y / module.PartDiagonal.Y,
currentPivotOrigin.Z / module.PartDiagonal.Z);
var currentPivotRelativeCenter = new Point3i(currentPivotCenter);
var firstModulePartRelativeCenter = module.PartCenters[0];
var modulePartsRelativeCentersRelativeToModulePivot = module.PartCenters.Select(center => center - firstModulePartRelativeCenter);

var currentModuleSlots = modulePartsRelativeCentersRelativeToModulePivot
.Zip(
module.PartNames,
(partCenter, partName) => new Slot(basePlane,
currentPivotRelativeCenter + partCenter,
currentRelativePosition + partCenter,
module.PartDiagonal,
false,
new List<string>() { module.Name },
Expand Down
33 changes: 28 additions & 5 deletions Components/Solver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public ComponentSolver( ) : base("Monoceros WFC Solver",
"Main") {
}

public override Guid ComponentGuid => new Guid("DD1A1FA6-ACD4-4202-8B1A-9840949644B3");
public override Guid ComponentGuid => new Guid("4C19261E-4137-41F5-A118-A977021B2FA2");

protected override System.Drawing.Bitmap Icon => Properties.Resources.solver;

Expand Down Expand Up @@ -48,6 +48,11 @@ protected override void RegisterInputParams(GH_Component.GH_InputParamManager pM
"Maximum Number of Solver Attempts",
GH_ParamAccess.item,
10);
pManager.AddIntegerParameter("Max Time",
"T",
"Maximum Time spent with Attempts (milliseconds). Negative and 0 = infinity",
GH_ParamAccess.item,
0);
pManager.AddBooleanParameter("Use Shannon Entropy",
"E",
"Whether to use Shannon Entropy instead of the simpler linear entropy calculations",
Expand Down Expand Up @@ -99,6 +104,7 @@ protected override void SolveInstance(IGH_DataAccess DA) {
var rulesRaw = new List<Rule>();
var randomSeed = 42;
var maxAttempts = 10;
var maxTime = 0;
bool useShannonEntropy = false;
var maxObservations = 0;

Expand Down Expand Up @@ -126,11 +132,15 @@ protected override void SolveInstance(IGH_DataAccess DA) {
return;
}

if (!DA.GetData(5, ref useShannonEntropy)) {
if (!DA.GetData(5, ref maxTime)) {
return;
}

if (!DA.GetData(6, ref useShannonEntropy)) {
return;
}

if (!DA.GetData(6, ref maxObservations)) {
if (!DA.GetData(7, ref maxObservations)) {
return;
}

Expand Down Expand Up @@ -456,6 +466,7 @@ protected override void SolveInstance(IGH_DataAccess DA) {
worldForSolver.ToList(),
randomSeed,
maxAttempts,
maxTime,
maxObservationsUint,
entropy,
out var solvedSlotPartsTree);
Expand Down Expand Up @@ -597,6 +608,7 @@ private Stats Solve(List<RuleForSolver> rules,
List<Slot> slots,
int randomSeed,
int maxAttemptsInt,
int maxTime,
uint maxObservations,
Entropy entropy,
out List<List<string>> worldSlotPartsTree) {
Expand All @@ -616,6 +628,8 @@ private Stats Solve(List<RuleForSolver> rules,
stats.ruleCount = (uint)rules.Count;
stats.entropy = entropy;

var limitTime = maxTime > 0;

//
// -- Adjacency rules --
//
Expand Down Expand Up @@ -826,6 +840,8 @@ private Stats Solve(List<RuleForSolver> rules,

uint attempts = 0;

var timeStart = DateTime.UtcNow;

unsafe {
while (true) {
observationResult = Native.wfc_observe(wfcWorldStateHandle,
Expand All @@ -837,7 +853,8 @@ private Stats Solve(List<RuleForSolver> rules,

if (observationResult == WfcObserveResult.Deterministic
|| observationResult == WfcObserveResult.Nondeterministic
|| attempts == maxAttempts) {
|| attempts == maxAttempts
|| limitTime && ((DateTime.UtcNow - timeStart).TotalMilliseconds > maxTime)) {
break;
}
Native.wfc_world_state_clone_from(wfcWorldStateHandle, wfcWorldStateHandleBackup);
Expand All @@ -863,7 +880,13 @@ private Stats Solve(List<RuleForSolver> rules,
stats.solveAttempts = attempts;

if (stats.contradictory) {
stats.report = "WFC solver failed to find solution within " + maxAttempts + " attempts";
if (attempts == maxAttempts) {
stats.report = "WFC solver failed to find solution within " + maxAttempts + " attempts";
} else if (limitTime && attempts < maxAttempts) {
stats.report = "WFC solver failed to find solution within time limit " + maxTime + " milliseconds";
} else {
stats.report = "WFC solver failed to find solution for unknown reason. Please report this error, including screenshots, Rhino file and Grasshopper file at [email protected]. Thank you!";
}
}

unsafe {
Expand Down
Loading

0 comments on commit 05f73a3

Please sign in to comment.