diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml
index 4f191442..dec1e598 100644
--- a/diylc/diylc-core/src/org/diylc/presenter/update.xml
+++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml
@@ -2897,6 +2897,10 @@
NEW_FEATURE
Added component-level caching that should speed up rendering dramatically. While in experimental phase, it will be exposed as "Enable Cache Boost" item in the "Config" menu so it can be turned off if it causes any issues
+
+
+ IMPROVEMENT
+ Improved performance math calculations used to model "Twisted Wires"
BUG_FIX
diff --git a/diylc/diylc-library/src/org/diylc/components/connectivity/TwistedWire.java b/diylc/diylc-library/src/org/diylc/components/connectivity/TwistedWire.java
index e0dbdc79..f5f5922b 100644
--- a/diylc/diylc-library/src/org/diylc/components/connectivity/TwistedWire.java
+++ b/diylc/diylc-library/src/org/diylc/components/connectivity/TwistedWire.java
@@ -30,6 +30,7 @@ import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.diylc.common.LineStyle;
@@ -314,23 +315,26 @@ public class TwistedWire extends AbstractCurvedComponent implements IConti
else
stroke = ObjectCache.getInstance().fetchBasicStroke(thickness);
- double segmentLength = thickness * 6;
+ double segmentLength = thickness * 6; // chosen empirically
// Convert to polygon
List polygon = new ArrayList();
for (CubicCurve2D curve : curves)
polygon.addAll(split(curve, segmentLength));
+
+ List> segments = new ArrayList>();
+ segments.add(new ArrayList());
+ segments.add(new ArrayList());
- List firstCurves = new ArrayList();
- List secondCurves = new ArrayList();
+ Path2D[] currentPaths = new Path2D[2];
+ currentPaths[0] = new Path2D.Double();
+ currentPaths[1] = new Path2D.Double();
+
+ double offset = thickness * 1.5; // chosen empirically
+ double rectSize = thickness * 3.5; // chosen empirically
- Path2D current1 = new Path2D.Double();
- Path2D current2 = new Path2D.Double();
- double offset = thickness * 1.5;
- double rectSize = thickness * 3.5;
-
- current1.moveTo(polygon.get(0).getX1(), polygon.get(0).getY1());
- current2.moveTo(polygon.get(0).getX1(), polygon.get(0).getY1());
+ currentPaths[0].moveTo(polygon.get(0).getX1(), polygon.get(0).getY1());
+ currentPaths[1].moveTo(polygon.get(0).getX1(), polygon.get(0).getY1());
// create curved paths for each segment of the twisted pair
for (int i = 0; i < polygon.size(); i++) {
@@ -338,61 +342,76 @@ public class TwistedWire extends AbstractCurvedComponent implements IConti
double centerX = (line.getX1() + line.getX2()) / 2;
double centerY = (line.getY1() + line.getY2()) / 2;
double theta = Math.atan2(line.getY2() - line.getY1(), line.getX2() - line.getX1());
-
+
double sign = i % 2 == 0 ? 1 : -1;
-
- double theta1 = theta - sign * Math.PI / 2;
- double theta2 = theta + sign * Math.PI / 2;
-
- current1.quadTo(centerX + offset * Math.cos(theta1),
- centerY + offset * Math.sin(theta1), line.getX2(), line.getY2());
- current2.quadTo(centerX + offset * Math.cos(theta2),
- centerY + offset * Math.sin(theta2), line.getX2(), line.getY2());
-
- if (i % 2 == 0 || i == polygon.size() - 1) {
- firstCurves.add(current1);
- current1 = new Path2D.Double();
- current1.moveTo(line.getX2(), line.getY2());
+ double[] thetas = new double[] { theta - sign * Math.PI / 2, theta + sign * Math.PI / 2};
+
+ int finalI = i;
+
+ // calculate curve segments in parallel
+ Arrays.stream(new Integer[] { 0, 1 }).parallel().forEach((j) -> {
+ currentPaths[j].quadTo(centerX + offset * Math.cos(thetas[j]),
+ centerY + offset * Math.sin(thetas[j]), line.getX2(), line.getY2());
+ if (finalI % 2 == 0 || finalI == polygon.size() - 1) {
+ segments.get(j).add(currentPaths[j]);
+ currentPaths[j] = new Path2D.Double();
+ currentPaths[j].moveTo(line.getX2(), line.getY2());
+ }
+ });
+ }
+
+ Area[] outputAreas = new Area[] { new Area(), new Area() };
+
+ // create stroked areas in parallel
+ Arrays.stream(new Integer[] { 0, 1 }).parallel().forEach((j) -> {
+ for (Path2D p : segments.get(j)) {
+ outputAreas[j].add(new Area(stroke.createStrokedShape(p)));
}
- if (i % 2 == 1 || i == polygon.size() - 1) {
- secondCurves.add(current2);
- current2 = new Path2D.Double();
- current2.moveTo(line.getX2(), line.getY2());
- }
- }
-
- // convert to Area
- for (Path2D p : firstCurves) {
- firstLeadArea.add(new Area(stroke.createStrokedShape(p)));
- }
- for (Path2D p : secondCurves) {
- secondLeadArea.add(new Area(stroke.createStrokedShape(p)));
- }
+ });
// at overlapping points, decide which lead goes on top and clear the overlapping area from the lead below
+ Integer[] idx = new Integer[polygon.size()];
for (int i = 0; i < polygon.size(); i++) {
+ idx[i] = i;
+ }
+
+ Arrays.stream(idx).parallel().forEach(i -> {
Line2D line = polygon.get(i);
- Area pointRect1 = new Area(new Rectangle2D.Double(line.getX1() - rectSize / 2, line.getY1() - rectSize / 2, rectSize, rectSize));
+ Area pointRect1 = new Area(new Rectangle2D.Double(line.getX1() - rectSize / 2,
+ line.getY1() - rectSize / 2, rectSize, rectSize));
if (i % 2 == 1) {
- pointRect1.intersect(firstLeadArea);
- secondLeadArea.subtract(pointRect1);
+ pointRect1.intersect(outputAreas[0]);
+ synchronized (outputAreas[1]) {
+ outputAreas[1].subtract(pointRect1);
+ }
} else {
- pointRect1.intersect(secondLeadArea);
- firstLeadArea.subtract(pointRect1);
- }
-
- if (i == polygon.size() - 1) {
- Area pointRect2 = new Area(new Rectangle2D.Double(line.getX2() - rectSize / 2, line.getY2() - rectSize / 2, rectSize, rectSize));
- if (i % 2 == 0) {
- pointRect2.intersect(firstLeadArea);
- secondLeadArea.subtract(pointRect2);
- } else {
- pointRect2.intersect(secondLeadArea);
- firstLeadArea.subtract(pointRect2);
+ pointRect1.intersect(outputAreas[1]);
+ synchronized (outputAreas[0]) {
+ outputAreas[0].subtract(pointRect1);
}
}
- }
+
+ if (i == polygon.size() - 1) {
+ Area pointRect2 = new Area(new Rectangle2D.Double(line.getX2() - rectSize / 2,
+ line.getY2() - rectSize / 2, rectSize, rectSize));
+ if (i % 2 == 0) {
+ pointRect2.intersect(outputAreas[0]);
+ synchronized (outputAreas[1]) {
+ outputAreas[1].subtract(pointRect2);
+ }
+ } else {
+ pointRect2.intersect(outputAreas[1]);
+ synchronized (outputAreas[0]) {
+ outputAreas[0].subtract(pointRect2);
+ }
+ }
+ }
+ });
+
+ // dump to the output areas
+ firstLeadArea.add(outputAreas[0]);
+ secondLeadArea.add(outputAreas[1]);
}
// splits a curve into a series of lines
diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java
index b6208306..461399ea 100644
--- a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java
+++ b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java
@@ -23,6 +23,7 @@ package org.diylc.swing.gui;
import java.awt.BorderLayout;
import java.awt.Container;
+import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
@@ -107,7 +108,8 @@ public class MainFrame extends JFrame implements ISwingUI {
public MainFrame() {
super("DIYLC 3");
- setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ setPreferredSize(new Dimension(1024, 700));
createBasePanels();
menuMap = new HashMap();
buttonGroupMap = new HashMap();