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

Rework Camera#easeTo and Transform#pointLocation #4320

Closed
wants to merge 37 commits into from

Conversation

joswinter
Copy link
Contributor

@joswinter joswinter commented Feb 22, 2017

The changes in this pull request fix #4178 and #3112.

bug description easing

Previously when using the Camera#easeTo function the following can happen where the camera only eases to the north. The path of the camera is illustrated in the left image where the camera moves across both poles when easing between two coordinates A and B, this is unwanted behavior. The easeTo should ease in the direction of the shortest path to the endpoint, The correct behaviour is illustrated in the right image. The incorrect behaviour can be observed in the following gist: https://bl.ocks.org/joswinter/f2e9bc8413ed00875ac06eca35d7b83d

These bugs were already fixed by @yeldarby in pull request #3130 which hasn't been merged. I continued working on the code in that pull request and reworked the structure of Camera#easeTo and Transform#pointLocation as commented by @lucaswoj. The Transform#pointLocation now has an optional viewport argument which is used to calculate the pointLocation for the given point. I also changed the Camera#easeTo implementation so that all tests in camera.test.js pass.

Copy link
Contributor

@lucaswoj lucaswoj left a comment

Choose a reason for hiding this comment

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

Thanks for the PR and sorry for the long hold time for a review. We've been very busy the last few weeks here @ Mapbox!

if ('center' in v) this.center = v.center;
if ('zoom' in v) this.zoom = v.zoom;
if ('bearing' in v) this.bearing = v.bearing;
if ('pitch' in v) this.pitch = v.pitch;
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 a step in the right direction. I'm still uncomfortable with this stateful & implicit setting & resetting of parameters on Transform. Passing arguments this way is surprising and creates potential for bugs & unintended side effects. coordinateLocation and coordinatePoint worked with this viewport object directly rather than reading from Transform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I get what you mean by that, I looked into it. I could add an extra argument to the coordinateLocation and pointCoordinate methods but the reason why I used this implicit setting of parameters is to make sure that the pixelMatrix is calculated for the viewport. What do you suggest that I should do concerning the _calcMatrices function, should I rewrite it to allow a viewport parameter? _calcMatrices uses the following fields:

  • this.x
  • this.y
  • this._fov
  • this._pitch
  • this.width
  • this.height
  • this.worldSize

If I'm going to do it with a viewport parameter I'm also a bit afraid to introduce a lot of duplicate code because every parameter has a seperate check and a lot of other Transform fields are dependent on the ones used in the viewport such as this.worldSize which is dependent on this.scale which is dependent on this.zoom.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally, we would work together to find a way to add a viewport parameter to _calcMatrices, reduce the amount of input needed by _calcMatricies where possible, and refactor to eliminate any duplicate code. Does that sound doable based on your experience in this part of the codebase?

src/ui/camera.js Outdated
tr.setLocationAtPoint(toLngLat, fromPoint.add(toPoint.sub(fromPoint)._mult(k)));
var lng = interpolate(startCenter.lng, toLocation.lng, k2);
var lat = interpolate(startCenter.lat, toLocation.lat, k2);
tr.center = LngLat.convert([lng, lat]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to write test for this new functionality? Regression tests for #3112 & #4178?

@joswinter
Copy link
Contributor Author

joswinter commented Mar 17, 2017

I created a regression test for the Camera#easeTo function. It seems to be working but it is not 100% exact. In the test, I check whether the camera is easing in the right direction in the case of this example.

I have added a _calcMatricesViewport and a _constrainViewport method which helps in calculating the inversePixelMatrix for a viewport @lucaswoj. However, there are two tests that keep failing:

  • the zooms with specified offset test.
  • the zooms with specified offset relative to viewport on a rotated camera test.

The functionality should be the same as it was in commit aaede, do the fields in transform.js are influenced by something else which differ in passing the viewport or in setting all transform fields manually from the viewport? The following code was changed in Transform but the output of the pointLocation method shouldn't change.

@joswinter
Copy link
Contributor Author

joswinter commented Mar 18, 2017

@lucaswoj I got the tests working now. I want to start refactoring now but I still have some questions. The first two things that should happen is that:

  • _constrain and _constrainViewport need to be merged. How should I merge these two because at the moment one of them returns something and the other sets the field of the class directly?
  • _calcMatrices and _calcMatricesViewport need to be merged. The same holds here, but I have some concerns with the projMatrix field that is being set in the middle of the _calcMatrices function.

Do you have any suggestion how I should merge these functions? For most of the functions in the Transform class I already added a viewport parameter and a line at the start with if (viewport === undefined) viewport = this.getViewport();, do you think that is the correct way to go?

edit: I refactored and merged the two functions.

Copy link
Contributor

@lucaswoj lucaswoj left a comment

Choose a reason for hiding this comment

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

Thank you so much for following through on the requested architectural changes. 🎉 😄

I have a few points of in-the-weeds feedback.

@jfirebaugh @mourner would you care to take a look at the algorithm changes?

this.xLng(zoomedCoord.column * this.tileSize),
this.yLat(zoomedCoord.row * this.tileSize));
x,
y);
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nit: can we make this all one line?

@@ -283,8 +314,10 @@ class Transform {
const coord0 = [p.x, p.y, 0, 1];
const coord1 = [p.x, p.y, 1, 1];

vec4.transformMat4(coord0, coord0, this.pixelMatrixInverse);
vec4.transformMat4(coord1, coord1, this.pixelMatrixInverse);
const pixelMatrixInverse = this._calcMatrices(viewport);
Copy link
Contributor

Choose a reason for hiding this comment

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

This operation is expensive enough that we will need to cache pixelMatrixInverse for at least the global viewport.

sx = maxX - minX < size.x ? size.x / (maxX - minX) : 0;
minX = (180 + this.lngRange[0]) * worldSize / 360;
maxX = (180 + this.lngRange[1]) * worldSize / 360;
sx = maxX - minX < this.size.x ? this.size.x / (maxX - minX) : 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain what changed & why in this algorithm?

@mourner
Copy link
Member

mourner commented Mar 20, 2017

Happy to see a PR that fixes the issues, but surprised that it requires so many complicated changes, many of them introducing new expensive calculations. I'll need to spend some time on the problem to see if there's possibly a more elegant approach we're not yet seeing.

@lucaswoj
Copy link
Contributor

@mourner Should we delay merging this PR until you've had a chance to prototype a more elegant implementation?

@mourner
Copy link
Member

mourner commented Mar 29, 2017

@lucaswoj yes, let's delay for a few days! We'll have a hard time maintaining the code merged as is.

@mourner
Copy link
Member

mourner commented Mar 29, 2017

I'm moving with an alternative PR, submitting soon. Basically the root problem here is not pointLocation implementation, it's that we use it for easeTo interpolations in the first place. Moving the approach closer to how flyTo works fixes the problem with simpler code changes, so I'll close the PR for now, but it'll be there in case we want to revisit making pointLocation more flexible. Thanks for working on this!

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.

easeTo goes in the wrong direction
4 participants