From 2c16b9949ab2b8cdbdbff7bbda91eb862b1176ed Mon Sep 17 00:00:00 2001 From: Marko Luukkainen Date: Tue, 3 Dec 2019 13:49:36 +0200 Subject: [PATCH] Revised update logic for inline components * PipingRules no longer have separate code for path legs with offsets * Overlapping fixed length components are forced to be separate, as long as there is available space * Using more stable calculation for turn component orientations (reusing path leg direction vector) * Error messages of overlapping inline components gitlab #14 gitlab #59 Change-Id: I85e754ffb6dab37ca7b7ae8e5f28842fbf095a11 (cherry picked from commit aca223a1616159645710d7c9ee67ed1a6bd47b99) --- .../controlpoint/PipeControlPoint.java | 32 +- .../scenegraph/controlpoint/PipingRules.java | 435 +++++++++++------- 2 files changed, 287 insertions(+), 180 deletions(-) diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java index 8616e703..abd899ac 100644 --- a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java @@ -529,26 +529,34 @@ public class PipeControlPoint extends G3DNode implements IP3DNode { return getControlPointOrientationQuat(dir, turnAxis, angle); } } + + public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle, boolean reversed) { + if (turnAxis == null) { + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + Quat4d q = getControlPointOrientationQuat(dir, angle); + if (reversed) { + Quat4d q2 = new Quat4d(); + q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI)); + q.mulInverse(q2); + } + return q; + } else { + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + return getControlPointOrientationQuat(dir, turnAxis, angle); + } + } public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) { if (turnAxis == null) { Vector3d dir = getPathLegDirection(Direction.NEXT); - if (dir.lengthSquared() > MathTools.NEAR_ZERO) - dir.normalize(); - Quat4d q = getControlPointOrientationQuat(dir, angle); - if (reversed) { - Quat4d q2 = new Quat4d(); - q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI)); - q.mulInverse(q2); - } - return q; + return getControlPointOrientationQuat(dir, angle, reversed); } else { Vector3d dir = getPathLegDirection(Direction.PREVIOUS); dir.negate(); - if (dir.lengthSquared() > MathTools.NEAR_ZERO) - dir.normalize(); - return getControlPointOrientationQuat(dir, turnAxis, angle); + return getControlPointOrientationQuat(dir, angle, reversed); } } diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java index 688e50f9..b64a9e93 100644 --- a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java @@ -16,6 +16,7 @@ import org.simantics.plant3d.scenegraph.PipelineComponent; import org.simantics.plant3d.scenegraph.TurnComponent; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; import org.simantics.plant3d.utils.ComponentUtils; +import org.simantics.utils.datastructures.Pair; import org.simantics.utils.ui.ErrorLogger; public class PipingRules { @@ -24,7 +25,7 @@ public class PipingRules { private static double MIN_TURN_ANGLE = 0.001; // Threshold for removing turn components. private static double ALLOWED_OFFSET = 0.001; // Allowed offset for directed path legs - private static double MIN_INLINE_LENGTH = 0.001; // Minimum length of inline components, when component removal is not allowed. + private static double MIN_INLINE_LENGTH = 0.0005; // Minimum length of inline components, when component removal is not allowed. private static final int REMOVE_NONE = 0; private static final int REMOVE_START = 1; @@ -477,11 +478,11 @@ public class PipingRules { if (asDirected(u.end, Direction.PREVIOUS)) directed++; if (rs) - u.start.getPipelineComponent().setError(null); + setErrorForce(u.start, null); if (re) - u.end.getPipelineComponent().setError(null); + setErrorForce(u.end, null); for (PipeControlPoint pcp : u.list) - pcp.getPipelineComponent().setError(null); + setErrorForce(pcp, null); switch (directed) { case 0: updateFreePathLeg(u, lengthChange); @@ -520,149 +521,96 @@ public class PipingRules { } boolean recalcline = false; - if (!u.hasOffsets) { - - - for (PipeControlPoint icp : u.list) { - updateInlineControlPoint(icp, start, end, u.dir); - - if (icp.isOffset()) { - // TODO : offset vector is already calculated and should be cached - Vector3d off = icp.getSizeChangeOffsetVector(u.dir); - updateOffsetPoint(icp, off); - } - } - if (!checkSizes) - return; - ArrayList pathLegPoints = new ArrayList(); - ArrayList fixedLengthPoints = new ArrayList(); - pathLegPoints.add(u.start); - - for (PipeControlPoint icp : u.list) { - // updateInlineControlPoint(icp, u.startPoint, - // u.endPoint,u.dir); - updateBranchControlPointBranches(icp); - pathLegPoints.add(icp); - if (!icp.isVariableLength()) - fixedLengthPoints.add(icp); + Vector3d sp = new Vector3d(start); + Vector3d ep = new Vector3d(end); + ep.sub(u.offset); + + + for (PipeControlPoint icp : u.list) { + updateInlineControlPoint(icp, sp, ep, u.dir); + if (icp.isOffset()) { + // TODO : offset vector is already calculated and should be cached + Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); + updateOffsetPoint(icp, offset); + sp.add(offset); + ep.add(offset); } - pathLegPoints.add(u.end); + } + + if (!checkSizes) + return; + + // Collect all path leg points for updating variable length components. This list will also contain leg ends (usually turns) + ArrayList pathLegPoints = new ArrayList<>(); + // Collect all fixed length components with their offsets. + ArrayList> fixedLengthPoints = new ArrayList<>(); + + pathLegPoints.add(u.start); + fixedLengthPoints.add(new Pair(u.start, new Vector3d())); + Vector3d off = new Vector3d(); + for (PipeControlPoint icp : u.list) { + pathLegPoints.add(icp); + updateBranchControlPointBranches(icp); + if (icp.isOffset()) { + fixedLengthPoints.add(new Pair(icp, new Vector3d(off))); + Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); + off.add(offset); + } else if (!icp.isVariableLength()) { + fixedLengthPoints.add(new Pair(icp, new Vector3d(off))); + } + } + pathLegPoints.add(u.end); + fixedLengthPoints.add(new Pair(u.end, new Vector3d(off))); + + sp = new Vector3d(start); + ep = new Vector3d(end); + ep.sub(u.offset); + + updateFixedLengths(fixedLengthPoints, sp, ep, u.dir); + + for (int i = 0; i < pathLegPoints.size(); i++) { + PipeControlPoint icp = pathLegPoints.get(i); - // updateInlineControlPoint keeps components between path leg ends, but does not ensure that fixed length components do no overlap each other + PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null; + PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null; - for (int i = 0; i < fixedLengthPoints.size(); i++) { - PipeControlPoint prev = i == 0 ? null : fixedLengthPoints.get(i-1); - PipeControlPoint curr = fixedLengthPoints.get(i); - PipeControlPoint next = i == fixedLengthPoints.size() -1 ? null : fixedLengthPoints.get(i+1); - updateFixedLength(curr, prev, next, start,end, u.dir); - } - - for (int i = 0; i < pathLegPoints.size(); i++) { - PipeControlPoint icp = pathLegPoints.get(i); - - PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null; - PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null; + if (prev != null && prev.isDualInline()) + prev = prev.getDualSub(); - if (icp.isVariableLength()) { - if (prev != null && next != null) { - - recalcline = recalcline | updateVariableLength(icp, prev, next); - - } else { - // this is variable length component at the end of the - // piperun. - // the problem is that we want to keep unconnected end - // of the component in the same - // place, but center of the component must be moved. - updateVariableLengthEnd(icp, prev != null ? prev : next); - } - - - } else if (prev != null && !prev.isVariableLength()) { - // If this and previous control point are not variable - // length pcps, we'll have to check if there is no empty - // space between them. - // I there is, we'll have to create new variable length - // component between them. - recalcline = recalcline | possibleVaribleLengthInsert(icp, prev); - } - } - } else { // with offset - Vector3d sp = new Vector3d(start); - Vector3d ep = new Vector3d(end); - ep.sub(u.offset); - - ArrayList pathLegPoints = new ArrayList(); - pathLegPoints.add(u.start); + if (icp.isVariableLength()) { + if (prev != null && next != null) { + recalcline = recalcline | updateVariableLength(icp, prev, next); - for (PipeControlPoint icp : u.list) { - updateInlineControlPoint(icp, sp, ep, u.dir); - updateBranchControlPointBranches(icp); - pathLegPoints.add(icp); - if (icp.isOffset()) { - // TODO : offset vector is already calculated and should be - // cached - Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); - updateOffsetPoint(icp, offset); - sp.add(offset); - ep.add(offset); + } else { + // this is variable length component at the end of the piperun. + // the problem is that we want to keep unconnected end of the component in the same + // place, but center of the component must be moved. + updateVariableLengthEnd(icp, prev != null ? prev : next); } + } else if (prev != null && !prev.isVariableLength()) { + // If this and previous control point are not variable length pcps, + // we'll have to check if there is no empty space between them. + // I there is, we'll have to create new variable length component between them. + recalcline = recalcline | possibleVaribleLengthInsert(icp, prev); } - pathLegPoints.add(u.end); - - if (!checkSizes) - return; - - sp = new Vector3d(u.startPoint); - ep = new Vector3d(u.endPoint); - ep.sub(u.offset); - - for (int i = 0; i < pathLegPoints.size(); i++) { - PipeControlPoint icp = pathLegPoints.get(i); - - PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null; - PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null; - - if (prev != null && prev.isDualInline()) - prev = prev.getDualSub(); - - - if (icp.isVariableLength()) { - if (prev != null && next != null) { - recalcline = recalcline | updateVariableLength(icp, prev, next); - - } else { - // this is variable length component at the end of the - // piperun. - // the problem is that we want to keep unconnected end - // of the component in the same - // place, but center of the component must be moved. - updateVariableLengthEnd(icp, prev != null ? prev : next); - } - } else if (prev != null && !prev.isVariableLength()) { - // If this and previous control point are not variable - // length pcps, we'll have to check if there is no empty - // space between them. - // I there is, we'll have to create new variable length - // component between them. - recalcline = recalcline | possibleVaribleLengthInsert(icp, prev); - } - if (icp.isOffset()) { - // TODO : offset vector is already calculated and should be - // cached - Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); - sp.add(offset); - ep.add(offset); - } + if (icp.isOffset()) { + // TODO : offset vector is already calculated and should be cached + Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); + sp.add(offset); + ep.add(offset); } } + if (recalcline) { u.list.clear(); u.start.findNextEnd(u.list); } if (checkSizes) { - double pathLegLength = MathTools.distance(u.startPoint, u.endPoint); + sp = new Vector3d(u.startPoint); + ep = new Vector3d(u.endPoint); + ep.sub(u.offset); + double pathLegLength = MathTools.distance(sp, ep); double availableLength = pathLegLength; if (u.start.isTurn()) availableLength -= u.start.getInlineLength(); @@ -673,32 +621,186 @@ public class PipingRules { availableLength-= pcp.getLength(); } if (availableLength < 0.0) { - u.start.getPipelineComponent().setError("Not enough available space"); - u.end.getPipelineComponent().setError("Not enough available space"); + setError(u.start, "Not enough available space"); + setError(u.end, "Not enough available space"); for (PipeControlPoint pcp : u.list) - pcp.getPipelineComponent().setError("Not enough available space"); + setError(pcp, "Not enough available space"); } // System.out.println(u.start.getPipelineComponent().toString() + " " + pathLegLength + " " + availableLength + " " + u.end.getPipelineComponent().toString() + " " + u.start.getInlineLength() + " " + u.end.getInlineLength()); } } - private static void updateFixedLength(PipeControlPoint icp, PipeControlPoint prev, PipeControlPoint next, Vector3d s, Vector3d e, Vector3d dir) { + private enum Gap{ATTACHED,OVERLAP,SPACE}; + + private static class GapObj { + Gap gap; + double d; + + Pair pcp1; + Pair pcp2; + } + + private static void updateFixedLengths(List> fixedLengthPoints, Vector3d s, Vector3d e, Vector3d dir) { + double totalLength = MathTools.distance(s, e); + double reservedLength = 0.0; + List distances = new ArrayList<>(fixedLengthPoints.size()); + distances.add(0.0); + for (int i = 1; i < fixedLengthPoints.size()-1; i++) { + Pair pcp = fixedLengthPoints.get(i); + reservedLength += pcp.first.getLength(); + Vector3d p = pcp.first.getWorldPosition(); + p.sub(pcp.second); + double d= MathTools.distance(s, p); + distances.add(d); + } + distances.add(totalLength); + + if (totalLength >= reservedLength) { + // There is enough space for all fixed length components. + List gaps = new ArrayList<>(fixedLengthPoints.size()-1); + int overlaps = 0; + // Analyze gaps between components + for (int i = 0; i < fixedLengthPoints.size()-1; i++) { + Pair pcp1 = fixedLengthPoints.get(i); + Pair pcp2 = fixedLengthPoints.get(i+1); + double d1 = distances.get(i); + double d2 = distances.get(i+1); + double ld1 = i == 0 ? 0.0 :pcp1.first.getInlineLength(); + double ld2 = i == fixedLengthPoints.size()-2 ? 0.0 : pcp2.first.getInlineLength(); + + double e1 = d1 + ld1; // End of comp1 + double s2 = d2 - ld2; // Start of comp2 + double diff =s2 - e1; + GapObj obj = new GapObj(); + obj.pcp1 = pcp1; + obj.pcp2 = pcp2; + obj.d = diff; + if (diff < -MIN_INLINE_LENGTH) { + obj.gap = Gap.OVERLAP; + overlaps++; + } else if (diff > MIN_INLINE_LENGTH) { + obj.gap = Gap.SPACE; + } else { + obj.gap = Gap.ATTACHED; + } + gaps.add(obj); + } + // If there are no overlaps, there is nothing to do. + if (overlaps == 0) + return; + // Get rid of overlapping components by using closest available free spaces. + for (int i = 0; i < gaps.size(); i++) { + GapObj gapObj = gaps.get(i); + if (gapObj.gap != Gap.OVERLAP) + continue; + double curr = gapObj.d; + int d = 1; + while (curr < -MIN_INLINE_LENGTH) { + GapObj next = i+d >= 0 ? gaps.get(i+d) : null; + GapObj prev = i-d >= 0 ? gaps.get(i-d) : null; + if (next != null && next.gap == Gap.SPACE) { + double move = Math.min(-curr, next.d); + curr+= move; + next.d -= move; + if (next.d < MIN_INLINE_LENGTH) + next.gap = Gap.ATTACHED; + Vector3d mv = new Vector3d(dir); + mv.normalize(); + mv.scale(move); + for (int j = i ; j < i+d; j++) { + Pair pcp = gaps.get(j).pcp2; + Vector3d p = new Vector3d(pcp.first.getWorldPosition()); + p.add(mv); + pcp.first.setWorldPosition(p); + } + } + if (curr < -MIN_INLINE_LENGTH && prev != null && prev.gap == Gap.SPACE) { + double move = Math.min(-curr, prev.d); + curr+= move; + next.d -= move; + if (next.d < MIN_INLINE_LENGTH) + next.gap = Gap.ATTACHED; + Vector3d mv = new Vector3d(dir); + mv.normalize(); + mv.scale(-move); + for (int j = i ; j > i-d; j--) { + Pair pcp = gaps.get(j).pcp1; + Vector3d p = new Vector3d(pcp.first.getWorldPosition()); + p.add(mv); + pcp.first.setWorldPosition(p); + } + } + } + } + } else { + for (int i = 1; i < fixedLengthPoints.size()-1; i++) { + Pair prev = i == 0 ? null : fixedLengthPoints.get(i-1); + Pair curr = fixedLengthPoints.get(i); + Pair next = i == fixedLengthPoints.size() -1 ? null : fixedLengthPoints.get(i+1); + updateFixedLength(curr, prev, next, s,e, dir); + } + } + } + + private static void updateFixedLength(Pair icp, Pair prev, Pair next, Vector3d s, Vector3d e, Vector3d dir) { if (prev != null) { - checkOverlap(icp, prev); + checkOverlap(prev, icp, dir,true); } if (next != null) - checkOverlap(icp, next); + checkOverlap(icp, next, dir,true); } - private static void checkOverlap(PipeControlPoint icp, PipeControlPoint prev) { - double d = MathTools.distance(prev.getWorldPosition(), icp.getWorldPosition()); - double r = icp.getInlineLength() + prev.getInlineLength(); - if (d < r) { - if (icp.getPipelineComponent().getError() == null) - icp.getPipelineComponent().setError("Overlapping"); - if (prev.getPipelineComponent().getError() == null) - prev.getPipelineComponent().setError("Overlapping"); + private static boolean checkOverlap(Pair icp, Pair icp2, Vector3d dir, boolean se) { + Vector3d p1 = icp.first.getWorldPosition(); + Vector3d p2 = icp2.first.getWorldPosition(); + p1.add(icp.second); + p2.add(icp2.second); + double u[] = new double[1]; + MathTools.closestPointOnStraight(p2, p1, dir, u); + if (u[0] < 0.0) { + p2.set(p1); + p2.sub(icp.second); + p2.add(icp2.second); + MathTools.mad(p2, dir, MIN_INLINE_LENGTH); + icp2.first.setWorldPosition(p2); + } + double d = MathTools.distance(p1, p2); + double r = icp.first.getInlineLength() + icp2.first.getInlineLength(); + + if ((d-r) < - MIN_INLINE_LENGTH) { + if (se) { + setError(icp.first, "Overlapping"); + setError(icp2.first, "Overlapping"); + } + return true; } + return false; + } + + /** + * Overrides current error of a component + * @param pcp + * @param error + */ + private static void setErrorForce(PipeControlPoint pcp, String error) { + PipelineComponent comp = pcp.getPipelineComponent(); + if (comp == null) + return; + comp.setError(error); + } + + /** + * Sets error for a component, if there is no existing error. + * @param pcp + * @param error + */ + private static void setError(PipeControlPoint pcp, String error) { + PipelineComponent comp = pcp.getPipelineComponent(); + if (comp == null) + return; + if (comp.getError() != null) + return; + comp.setError(error); } private static boolean updateVariableLength(PipeControlPoint icp, PipeControlPoint prev, PipeControlPoint next) { @@ -725,7 +827,7 @@ public class PipingRules { if (icp.isDeletable()) { if (!allowInsertRemove) { icp.setLength(MIN_INLINE_LENGTH); - icp.getPipelineComponent().setError("Not enough available space"); + setError(icp, "Not enough available space"); triedIR = true; return false; } @@ -1376,26 +1478,26 @@ public class PipingRules { updateTurnControlPointTurn(u.start, null, null); // updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE); } else if (u.start.isEnd()) { - updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint); + updateEndComponentControlPoint(u.start, u.dir); } else if (u.start.isInline()) { - updateControlPointOrientation(u.start); + updateControlPointOrientation(u.start, u.dir); } if (u.end.isTurn()) { //updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext()); updateTurnControlPointTurn(u.end, null, null); // updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE); } else if (u.end.isEnd()) { - updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint); + updateEndComponentControlPoint(u.end, u.dir); } else if (u.end.isInline()) { - updateControlPointOrientation(u.end); + updateControlPointOrientation(u.end, u.dir); } } else { if (u.start.isEnd()) { - updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint); + updateEndComponentControlPoint(u.start, u.dir); } if (u.end.isEnd()) { - updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint); + updateEndComponentControlPoint(u.end, u.dir); } } if (updateInline) @@ -1516,7 +1618,7 @@ public class PipingRules { System.out.println(" " + newInlinePoint); icp.setWorldPosition(newInlinePoint); - updateControlPointOrientation(icp); + updateControlPointOrientation(icp, dir); } /** @@ -1528,12 +1630,12 @@ public class PipingRules { * @param nextPoint * @param prevPoint */ - private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d start, Vector3d end) throws Exception { + private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d dir) throws Exception { if (DEBUG) System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp); //FIXME : end control point cannot be fixed! //if (!ecp.isFixed()) - updateControlPointOrientation(ecp); + updateControlPointOrientation(ecp, dir); for (PipeControlPoint pcp : ecp.getChildPoints()) { // TODO update position @@ -1541,21 +1643,18 @@ public class PipingRules { } } - private static void updateControlPointOrientation(PipeControlPoint pcp) { - // FIXME : hack to bypass variable length components orientation -// if (pcp.getAtMostOneRelatedObject(ProcessResource.g3dResource.HasWorldOrientation) == null) -// return; -// if (pcp.rotationAngle == null) -// return; + private static void updateControlPointOrientation(PipeControlPoint pcp, Vector3d dir) { Double angleO = pcp.getRotationAngle(); double angle = 0.0; if (angleO != null) angle = angleO; - Boolean reversedO = pcp.getReversed(); - boolean reversed = false; - if (reversedO != null) - reversed = reversedO; - Quat4d q = pcp.getControlPointOrientationQuat(angle, reversed); + boolean reversed = pcp._getReversed(); + Quat4d q = null; + if (dir != null) { + q = pcp.getControlPointOrientationQuat(dir, angle, reversed); + } else { + q = pcp.getControlPointOrientationQuat(angle, reversed); + } pcp.setWorldOrientation(q); } @@ -1624,7 +1723,7 @@ public class PipingRules { tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS)); } - updateControlPointOrientation(tcp); + updateControlPointOrientation(tcp,prev); if (DEBUG) System.out.println("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis); -- 2.47.1