Skip to content

Commit 0e93c5b

Browse files
committed
Create a provisional PrecisionPointList to better handle rounding errors
When translating a PointList through multiple scalable layers, rounding errors accumulate over time because the remainder is discarded after each layer. For other shapes such as Points and Rectangles, this is solved by using a "Precision" variant. With this change, such a variant now also exists for the PointList and is used when calling "translateToAbsolute()" or "translateToRelative()" on a Figure. Note: The implementation of this PrecisionPointList is not complete and currently only works for this specific use case. The double-array doesn't update itself when the int-array is modified. This class should therefore not yet be used outside Draw2D. Relates to #829
1 parent 9103503 commit 0e93c5b

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/PrecisionTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import org.eclipse.draw2d.FigureUtilities;
1818
import org.eclipse.draw2d.IFigure;
1919
import org.eclipse.draw2d.ScalableLayeredPane;
20+
import org.eclipse.draw2d.geometry.PointList;
21+
import org.eclipse.draw2d.geometry.PrecisionPointList;
2022
import org.eclipse.draw2d.geometry.PrecisionRectangle;
2123
import org.eclipse.draw2d.geometry.Rectangle;
2224

@@ -86,4 +88,24 @@ public void testPreciseTranslateToRelative() {
8688
assertEquals(r1.width, r2.width);
8789
assertEquals(r1.height, r2.height);
8890
}
91+
92+
@Test
93+
public void testPreciseTranslateToAbsolute_PointList() {
94+
PointList p1 = new PointList(new int[] { 13, 29, 32, 5 });
95+
PointList p2 = new PrecisionPointList(new int[] { 13, 29, 32, 5 });
96+
fig.translateToAbsolute(p1);
97+
fig.translateToAbsolute(p2);
98+
assertArrayEquals(p1.toIntArray(), new int[] { 493, 1100, 1214, 189 });
99+
assertArrayEquals(p1.toIntArray(), p2.toIntArray());
100+
}
101+
102+
@Test
103+
public void testPreciseTranslateToRelative_PointList() {
104+
PointList p1 = new PointList(new int[] { 518, 628, 715, 313 });
105+
PointList p2 = new PrecisionPointList(new int[] { 518, 628, 715, 313 });
106+
fig.translateToRelative(p1);
107+
fig.translateToRelative(p2);
108+
assertArrayEquals(p1.toIntArray(), new int[] { 13, 16, 18, 8 });
109+
assertArrayEquals(p1.toIntArray(), p2.toIntArray());
110+
}
89111
}

org.eclipse.draw2d/src/org/eclipse/draw2d/Figure.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@
3333
import org.eclipse.draw2d.geometry.Dimension;
3434
import org.eclipse.draw2d.geometry.Insets;
3535
import org.eclipse.draw2d.geometry.Point;
36+
import org.eclipse.draw2d.geometry.PointList;
3637
import org.eclipse.draw2d.geometry.PrecisionDimension;
3738
import org.eclipse.draw2d.geometry.PrecisionPoint;
39+
import org.eclipse.draw2d.geometry.PrecisionPointList;
3840
import org.eclipse.draw2d.geometry.PrecisionRectangle;
3941
import org.eclipse.draw2d.geometry.Rectangle;
4042
import org.eclipse.draw2d.geometry.Translatable;
@@ -2096,6 +2098,8 @@ private Translatable toPreciseShape(Translatable source) {
20962098
return new PrecisionDimension(d);
20972099
} else if (source instanceof Rectangle r && !(source instanceof PrecisionRectangle)) {
20982100
return new PrecisionRectangle(r);
2101+
} else if (source instanceof PointList p && !(source instanceof PrecisionPointList)) {
2102+
return new PrecisionPointList(p);
20992103
}
21002104
}
21012105
// is already precise or doesn't have a precise variant
@@ -2120,6 +2124,8 @@ private void fromPreciseShape(Translatable source, Translatable target) {
21202124
d2.setSize(d1.width, d1.height);
21212125
} else if (source instanceof PrecisionRectangle r1 && target instanceof Rectangle r2) {
21222126
r2.setBounds(r1.x, r1.y, r1.width, r1.height);
2127+
} else if (source instanceof PrecisionPointList p1 && target instanceof PointList p2) {
2128+
System.arraycopy(p1.toIntArray(), 0, p2.toIntArray(), 0, p2.size() * 2);
21232129
}
21242130
}
21252131
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Patrick Ziegler and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Patrick Ziegler - initial API and implementation
12+
*******************************************************************************/
13+
14+
package org.eclipse.draw2d.geometry;
15+
16+
import java.util.Arrays;
17+
18+
/**
19+
* A PointList implementation using floating point values which are truncated
20+
* into the inherited integer fields. The use of floating point prevents
21+
* rounding errors from accumulating.
22+
*
23+
* <strong>EXPERIMENTAL</strong> This class has been added as part of a work in
24+
* progress and there is no guarantee that this API will remain unchanged. This
25+
* is likely to not function properly outside some very specific use-cases.
26+
*
27+
* @since 3.21
28+
* @noreference This class is not intended to be referenced by clients.
29+
*/
30+
public final class PrecisionPointList extends PointList {
31+
private static final Point PRIVATE_POINT = new Point();
32+
private double[] precisePoints = {};
33+
34+
/**
35+
* Constructs a PrecisionPointList with the given points.
36+
*
37+
* @param points int array where two consecutive ints form the coordinates of a
38+
* point
39+
*/
40+
public PrecisionPointList(int[] points) {
41+
super(points);
42+
precisePoints = Arrays.stream(points).asDoubleStream().toArray();
43+
}
44+
45+
/**
46+
* Constructs a PrecisionPointList with the given points.
47+
*
48+
* @param points PointList from which the initial values are taken
49+
*/
50+
public PrecisionPointList(PointList points) {
51+
this(points.getCopy().toIntArray());
52+
}
53+
54+
@Override
55+
public void performScale(double factor) {
56+
for (int i = 0; i < precisePoints.length; ++i) {
57+
precisePoints[i] *= factor;
58+
}
59+
for (int i = 0; i < size(); ++i) {
60+
updateIntPoint(i);
61+
}
62+
}
63+
64+
/**
65+
* Updates the int-point at the given index using its precise coordinates.
66+
*/
67+
private void updateIntPoint(int i) {
68+
getPoint(PRIVATE_POINT, i);
69+
int preciseX = PrecisionGeometry.doubleToInteger(precisePoints[i * 2]);
70+
int preciseY = PrecisionGeometry.doubleToInteger(precisePoints[i * 2 + 1]);
71+
if (preciseX != PRIVATE_POINT.x || preciseY != PRIVATE_POINT.y) {
72+
PRIVATE_POINT.x = preciseX;
73+
PRIVATE_POINT.y = preciseY;
74+
setPoint(PRIVATE_POINT, i);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)