Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING: remove absolute true/false from the api. #9395

Merged
merged 24 commits into from
Nov 6, 2023

Conversation

asturur
Copy link
Member

@asturur asturur commented Oct 1, 2023

Motivation

Fabric is currently maintaining 2 sets of coordinates.
One that is a scene/design coordinate space and another that is the screen/viewport space.
The scene is called aCoords, while the screen/viewport are both oCoords and lineCoords.
Those 2 sets aren't simply connected by the viewport trasnformation but also they have the addition of padding that always threw a wrench in calculations and predictability of the api.

Each api of the geometry topic ( containsPoint, containsObject, getBoundingBox ) had an option to be called with a boolean called absolute. In case the boolean was true, calculation were made around aCoords, while with false around lineCoords.

The issue was that lineCoords included padding, and that meant that depending on the padding level of controls those intersection would trigger also over the padding area.
This created a bleeding between 2 different piece of logic, pure object geometry and selection/targeting functionalities.

Padding adds some pixels around the natural bounding box of the object either for aesthetic reasons or in order to facilitate the object selection when an object is too small.
Adding padding to an object doesn't make it larger or covering more area or intersecting with more objects.

So now padding is out of all the geometries.

to make an example, scaleToWidth would scale an object to X pixels, on screen pixels, including padding.
If you had padding set to 10 to an object, you would get the bounding box of the object including padding, then you would divide that by the scaledWidth of the objet to get a scale factor.
Imagine the scale factor was 0.5, you would scale down your object by half, but padding never scales, so again your object would always be either too small ( because you don't expect and you don't see padding ) or too large ( because you expect padding but padding doesn't get scaled by object scaling ).

object.lineCoords have no reason to exists and are gone
All the api that required absolute=true to be specified are loosing absolute option and that is true by default.
We are slowly stopping referring to those coordinates as absolute since it was also confusing.

List of affect apis:

  • _getCoords
  • getCoords
  • intersectsWithRect
  • intersectsWithObject
  • isContainedWithinObject
  • isContainedWithinRect
  • containsPoint
  • getBoundingRect
  • scaleToWidth
  • scaleToHeight

Padding calculation is now a concern of the canvas and for now is not cached.
If you have an object with padding, every time you are selecting it or running a target check over it, you will run this calculation on top of the normal ones:

    const padding = obj.padding / viewportZoom;
    if (padding) {
      const [tl, tr, br, bl] = coords;
      // what is the angle of the object?
      // we could use getTotalAngle, but is way easier to look at it
      // from how coords are oriented, since if something went wrong
      // at least we are consistent.
      const angleRadians = Math.atan2(tr.y - tl.y, tr.x - tl.y),
        cosP = cos(angleRadians) * padding,
        sinP = sin(angleRadians) * padding,
        cosPSinP = cosP + sinP,
        cosPMinusSinP = cosP - sinP;

      coords = [
        new Point(tl.x - cosPMinusSinP, tl.y - cosPSinP),
        new Point(tr.x + cosPSinP, tr.y - cosPMinusSinP),
        new Point(br.x + cosPMinusSinP, br.y + cosPSinP),
        new Point(bl.x - cosPSinP, bl.y + cosPMinusSinP),
      ];

The code has been written with the idea of minimizing the calculation and to be consistent with the status of aCoords.
If aCoords are out of sync, padding will be out of sync too, that is ok.

Testing

This pr adds a new method that could be tested a bit more rigorously and requires manual verification since i conducted manual tests with objects and group but not too deeply

@github-actions
Copy link
Contributor

github-actions bot commented Oct 1, 2023

Build Stats

file / KB (diff) bundled minified
fabric 909.615 (-0.723) 304.506 (-0.570)

@asturur
Copy link
Member Author

asturur commented Oct 1, 2023

replace #9373

@asturur asturur marked this pull request as draft October 2, 2023 07:48
@asturur
Copy link
Member Author

asturur commented Oct 2, 2023

i need to add back some debug code so we can see against what we are targeting objects.

@asturur asturur marked this pull request as ready for review October 27, 2023 22:25
@asturur
Copy link
Member Author

asturur commented Oct 27, 2023

Found the small error, now i have to write a test that is clear in the visual and the intent

@asturur
Copy link
Member Author

asturur commented Oct 28, 2023

Ok this is ready for review.

@ShaMan123
Copy link
Contributor

ShaMan123 commented Nov 2, 2023

adding adds some pixels around the natural bounding box of the object either for aesthetic reasons or in order to facilitate the object selection when an object is too small.
Adding padding to an object doesn't make it larger or covering more area or intersecting with more objects.

Rearding facilitate the object selection when an object is too small I don't think this is supported any longer with this PR
I see you addressed this in the PR

All the description is correct only for canvas level objects.
For objects under groups lineCoords were always wrong because the padding that was added to them would be then transformed by the group plane in getCoords(false)

Copy link
Contributor

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments, some asking for changes

test('Selection hit regions', async ({ page }) => {
const canvasUtil = new CanvasUtil(page);
// prepare some common functions
await canvasUtil.executeInBrowser((canvas) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there was an officail way to add window methods but I see that page.exposeBinding/exposeFunction run in the playwright context and not the window context

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I think instead you could use evaluateHandle and then you can pass the handle back to the window methods and execute it.

Nevermind though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just wanted it to work without duplicating code between the different tests.
This would be probably something to do in the index.ts file of the test rather than the test itself.

I don't think is very important now.

});

await canvasUtil.executeInBrowser((canvas) => {
const group = canvas.getObjects()[0] as fabric.Group;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use the ObjectUtil instead of the canvas util

src/Collection.ts Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should revert this file in order to avoid conflicts
It is dead and handled by #8499

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok i can do that.

src/shapes/Object/ObjectGeometry.ts Outdated Show resolved Hide resolved
test/unit/object_geometry.js Outdated Show resolved Hide resolved
test/unit/object_geometry.js Outdated Show resolved Hide resolved
test/unit/object_geometry.js Show resolved Hide resolved
Comment on lines +764 to +797
private _pointIsInObjectSelectionArea(obj: FabricObject, point: Point) {
// getCoords will already take care of group de-nesting
let coords = obj.getCoords();
const viewportZoom = this.getZoom();
const padding = obj.padding / viewportZoom;
if (padding) {
const [tl, tr, br, bl] = coords;
// what is the angle of the object?
// we could use getTotalAngle, but is way easier to look at it
// from how coords are oriented, since if something went wrong
// at least we are consistent.
const angleRadians = Math.atan2(tr.y - tl.y, tr.x - tl.x),
cosP = cos(angleRadians) * padding,
sinP = sin(angleRadians) * padding,
cosPSinP = cosP + sinP,
cosPMinusSinP = cosP - sinP;

coords = [
new Point(tl.x - cosPMinusSinP, tl.y - cosPSinP),
new Point(tr.x + cosPSinP, tr.y - cosPMinusSinP),
new Point(br.x + cosPMinusSinP, br.y + cosPSinP),
new Point(bl.x - cosPSinP, bl.y + cosPMinusSinP),
];
// in case of padding we calculate the new coords on the fly.
// otherwise we have to maintain 2 sets of coordinates for everything.
// we can reiterate on storing those on something similar to lineCoords
// if this is slow, for now the semplification is large and doesn't impact
// rendering.
// the idea behind this is that outside target check we don't need ot know
// where those coords are
}
return Intersection.isPointInPolygon(point, coords);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind if I simplify the logic here using a padding vector?
Does it have to be private?
Can we instead change containsPoint to accept a 3rd param padding defaulting to 0?
I think that is much better both for fabric and for the dev

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my issue here is that Intersection is not a selection thing.
This is a method entirely done to check the selection and padding is selection only.
Why would you use containsPoint with padding? use checkTarget right away no?
if is padding is a selection thing.

Why would you use containsPoint instead of checkTarget?

Regarding private, i did that because i thought, let's see how it shapes out, become public is easier, or maybe then we need to change it radically or change its interface, and being private is a lesser deal.

Regarding the logic, i would like to see it first, so that we can compare is actually simpler.
This was just porting over what was before somewhere else, i m not attached to it.

Just dump how you would write this whole method in a comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// expose as const, can be reused by other parts
    const CORNER_ORIGIN = {
      tl: new Point(-0.5, -0.5),
      tr: new Point(0.5, -0.5),
      br: new Point(0.5, 0.5),
      bl: new Point(-0.5, 0.5),
    };

// expose in ObjectGeometry
  containsViewportPoint(point: Point, padding = 0) {
    const vpt = this.getViewportTransform();
    const paddingVector = new Point().scalarMultiply(padding * 2);
    const [tl, tr, br, bl] = this.getCoords();
    const viewportCoords = (
      Object.entries({ tl, tr, br, bl }) as [
        keyof typeof CORNER_ORIGIN,
        Point
      ][]
    ).map(([key, point]) =>
      sendPointToPlane(point, vpt).add(
        paddingVector.multiply(CORNER_ORIGIN[key])
      )
    );

    return Intersection.isPointInPolygon(point, viewportCoords);
  }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot to say about this.

Is better explained in person but i can summarize that Object geometry is done with padding and viewport. We just remove the concept that padding is part of the object geometry, is just a selection concern, and saying 'containsViewportPoint' and adding back padding is rolling back the change.

Also in this example i don't understand who is rotating the padding vector. I see we are using a direction vector to correctly move the point in that direction, but where is the rotation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but where is the rotation?

I missed that

We just remove the concept that padding is part of the object geometry, is just a selection concern, and saying 'containsViewportPoint' and adding back padding is rolling back the change.

I have much to say about that. I have said that geometry must occur in the viewport but you did not take that into account so I don't see as rolling back

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember you saying that, or we wouldn't have remove the absolute from everywhere.
Anyway, next meeting then.
I l freeze everything meanwhile

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this is extensive enough
What about using the same tehnique as the e2e test? Iterating over all points and calling the method and storing the result somehow?
Then it is a pixel perfect test and the test can e reduced in size using each I guess

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test covers most of the point at the edge of selection, and cover cases with groups, padding, skew, strokeUniform.
I think is fine, and if it doesn't cover something that will bug out, we will extend the test.

@asturur
Copy link
Member Author

asturur commented Nov 4, 2023

This is ready to go.

Copy link
Contributor

@ShaMan123 ShaMan123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take a look at my suggestion for containsViewportPoint

* /**
* Moves an object to specified level in stack of drawn objects
* @param {TBBox} bbox a bounding box in scene coordinates
* @param {{ includeIntersecting?: boolean }} options an object with includeIntersecting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not very useful info but the above it so that should be moved here IMO

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include objects that are contained in or intersect the bounding box

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that i wanted to copy a parameter section from another function and i pulled in a piece of comment without noticing. removing it.

@@ -311,13 +311,16 @@ export function createCollectionMixin<TBase extends Constructor>(Base: TBase) {
* Given a bounding box, return all the objects of the collection that are contained in the bounding box.
* If `includeIntersecting` is true, return also the objects that intersect the bounding box as well.
* This is meant to work with selection. Is not a generic method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This is meant to work with selection. Is not a generic method.

Why can't it be used in general? It is a good piece of logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was written for intersection on the canvas with the drag rectangle.
We are not promoting it now, promoting is possible without breaking later.

Copy link
Contributor

github-actions bot commented Nov 5, 2023

Coverage after merging remove-absolute-globally into master will be

82.82%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
index.node.ts7.69%100%0%14.29%17, 20, 23, 35, 38, 41
src
   ClassRegistry.ts100%100%100%100%
   Collection.ts94.71%94.64%86.67%97.09%101, 104, 207–208, 233–234
   CommonMethods.ts96.55%87.50%100%100%9
   Intersection.ts100%100%100%100%
   Observable.ts87.76%85.29%87.50%89.58%134–135, 160–161, 32–33, 41, 50, 78, 89
   Point.ts100%100%100%100%
   Shadow.ts98.36%95.65%100%100%178
   cache.ts97.06%90%100%100%57
   config.ts75%66.67%66.67%82.76%130, 138, 138, 138, 138, 138–140, 151–153
   constants.ts100%100%100%100%
src/Pattern
   Pattern.ts92.31%91.89%90%93.55%119, 130, 139, 32, 95
src/brushes
   BaseBrush.ts100%100%100%100%
   CircleBrush.ts0%0%0%0%108, 108, 108, 110, 112, 114–116, 118–121, 128–129, 13, 136, 138, 23–24, 32–36, 40–44, 51–54, 62–66, 68, 76, 76, 76, 76, 76–77, 79, 79, 79–82, 84, 92–93, 95, 97–99
   PatternBrush.ts97.06%87.50%100%100%21
   PencilBrush.ts91.06%82.35%100%93.81%122–123, 152, 152–154, 176, 176, 276, 280, 285–286, 68–69, 84–85
   SprayBrush.ts0%0%0%0%107, 107, 107, 107, 107–108, 110–111, 118–119, 121, 123–127, 136, 140–141, 141, 149, 149, 149–152, 154–157, 161–162, 164, 166–169, 17, 172, 179, 18, 180, 182, 184–185, 187, 194–195, 197–198, 20, 201, 201, 208, 208, 21, 212, 22, 22, 22–24, 28, 32, 39, 46, 53, 60, 67, 84–86, 94–96, 98–99
src/canvas
   Canvas.ts78.99%76.67%83.05%80.04%1007, 1007, 1007–1009, 1009, 1009, 1016–1017, 1025–1026, 1026, 1026–1027, 1033, 1035, 1063–1065, 1068–1069, 1073–1074, 1197–1199, 1202–1203, 1276, 1393, 1515, 159, 184, 291–292, 295–299, 304, 327–328, 333–338, 358, 358, 358–359, 359, 359–360, 368, 373–374, 374, 374–375, 377, 386, 392–393, 393, 393, 43, 436, 444, 448, 448, 448–449, 451, 47, 533–534, 534, 534–535, 541, 541, 541–543, 563, 565, 565, 565–566, 566, 566, 569, 569, 569–570, 573, 582–583, 585–586, 588, 588–589, 591–592, 604–605, 605, 605–606, 608–613, 619, 626, 663, 663, 663, 665, 667–672, 678, 684, 684, 684–685, 687, 690, 695, 708, 736, 736, 797–798, 798, 798, 798, 798, 798, 801–802, 805, 805–807, 810–811, 887, 899, 906, 906, 906, 919, 952, 973–974, 990–991, 991, 991–993, 996–997, 997, 997, 999
   CanvasOptions.ts100%100%100%100%
   SelectableCanvas.ts93.21%91.60%94.44%94.22%1013, 1021, 1140, 1142, 1144–1145, 302, 472–473, 475–476, 476, 476, 525–526, 587–588, 601, 641–643, 675, 680–681, 708–709, 769–770, 775–779, 781, 940, 940–941, 944, 964, 964
   StaticCanvas.ts96.78%93.09%100%98.53%1031, 1041, 1093–1094, 1097, 1132–1133, 1209, 1218, 1218, 1222, 1222, 1269–1270, 187–188, 204, 571, 583–584, 914–915, 915, 915–916
   StaticCanvasOptions.ts100%100%100%100%
   TextEditingManager.ts84.31%71.43%91.67%88%17–18, 18, 18–19, 19, 19
src/canvas/DOMManagers
   CanvasDOMManager.ts95.52%70%100%100%21–22, 29
   StaticCanvasDOMManager.ts97.50%88.89%100%100%33
   util.ts86.67%80.56%83.33%93.94%14, 26, 63–64, 67, 67, 74, 93–94
src/color
   Color.ts94.96%91.67%96.30%96.05%233, 258–259, 267–268, 48
   color_map.ts100%100%100%100%
   constants.ts100%100%100%100%
   util.ts85.71%76.92%100%89.74%55–56, 56, 58, 58, 58–59, 61–62, 89
src/controls
   Control.ts94.44%93.10%91.67%96.77%183, 249, 354
   changeWidth.ts100%100%100%100%
   commonControls.ts100%100%100%100%
   controlRendering.ts81.63%78%100%84.78%106, 111, 121, 121, 45, 50, 61, 61, 65–72, 81–82
   drag.ts100%100%100%100%
   fireEvent.ts88.89%75%100%100%13
   polyControl.ts7.25%0%0%13.51%103, 108, 120, 120, 120, 120, 120, 122–125, 125, 128, 135, 17, 25–29, 29, 29, 29, 29, 29, 29, 29, 50–56, 56, 56, 56, 56, 58, 63–64, 66, 76, 82–84, 84, 86, 89–90, 90, 90, 90, 90, 92, 97
   rotate.ts19.57%12.50%50%21.43%41, 45, 51, 51, 51–52, 55–57, 59, 59, 59, 59, 59–61, 61, 61–63, 65, 65, 65–67, 67, 67–68, 73, 73, 73–74, 76, 78, 80–81
   scale.ts93.57%92.94%100%93.67%129–130, 132–134, 148–149, 181–183, 42
   scaleSkew.ts78.79%64.29%100%85.71%27, 29, 29, 29, 31, 33, 35
   skew.ts91.03%79.31%100%97.67%131–132, 163–164, 171, 177, 179
   util.ts100%100%100%100%
   

@asturur
Copy link
Member Author

asturur commented Nov 6, 2023

follow up to change the padding calculation to use more matrix and vector names

@asturur
Copy link
Member Author

asturur commented Nov 6, 2023

@ShaMan123 approved in meeting

@asturur asturur merged commit 6849a15 into master Nov 6, 2023
22 checks passed
@asturur asturur deleted the remove-absolute-globally branch November 6, 2023 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants