Skip to content

Commit

Permalink
Enable clipping to viewport when converting coordinates to screen pos…
Browse files Browse the repository at this point in the history
…ition (#2179)

* Add lngLatToScreenPositionClipped

Enables projecting points to screen positions 'clipped' to the edge of the viewport

* Add logic in GlfwApp to test lngLatToScreenPositionClipped

* Add lngLatToScreenPosition clipping to Android interface

* Add lngLatToScreenPosition clipping to iOS interface

* Fixup clipping test code in GlfwApp

* Rename 'coordinateIsInViewport' to improve translated Swift method

Previous name translated as coordinateIs(inViewport:), new name translated as viewportContains(coordinate:)
  • Loading branch information
matteblair authored Jul 21, 2020
1 parent bc8654d commit 1671b1b
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 77 deletions.
2 changes: 1 addition & 1 deletion core/include/tangram/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class Map {
// Given longitude and latitude coordinates, set the output coordinates to the
// corresponding point in screen space (x right, y down); returns false if the
// point is not visible on the screen, otherwise returns true
bool lngLatToScreenPosition(double _lng, double _lat, double* _x, double* _y);
bool lngLatToScreenPosition(double _lng, double _lat, double* _x, double* _y, bool clipToViewport = false);

// Add a tile source for adding drawable map data, which will be styled
// according to the scene file using the provided data source name;
Expand Down
17 changes: 6 additions & 11 deletions core/src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,19 +638,14 @@ bool Map::screenPositionToLngLat(double _x, double _y, double* _lng, double* _la
return intersection;
}

bool Map::lngLatToScreenPosition(double _lng, double _lat, double* _x, double* _y) {
bool clipped = false;
bool Map::lngLatToScreenPosition(double _lng, double _lat, double* _x, double* _y, bool clipToViewport) {
bool outsideViewport = false;
glm::vec2 screenPosition = impl->view.lngLatToScreenPosition(_lng, _lat, outsideViewport, clipToViewport);

glm::vec2 screenCoords = impl->view.lngLatToScreenPosition(_lng, _lat, clipped);
*_x = screenPosition.x;
*_y = screenPosition.y;

*_x = screenCoords.x;
*_y = screenCoords.y;

float width = impl->view.getWidth();
float height = impl->view.getHeight();
bool withinViewport = *_x >= 0. && *_x <= width && *_y >= 0. && *_y <= height;

return !clipped && withinViewport;
return !outsideViewport;
}

void Map::setPixelScale(float _pixelsPerPoint) {
Expand Down
37 changes: 6 additions & 31 deletions core/src/util/geom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,37 +68,12 @@ float pointSegmentDistance(const glm::vec2& _p, const glm::vec2& _a, const glm::
return sqrt(sqPointSegmentDistance(_p, _a, _b));
}

glm::vec4 worldToClipSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition) {
return _mvp * _worldPosition;
}

glm::vec2 clipToScreenSpace(const glm::vec4& _clipCoords, const glm::vec2& _screenSize) {
glm::vec2 halfScreen = glm::vec2(_screenSize * 0.5f);

// from normalized device coordinates to screen space coordinate system
// top-left screen axis, y pointing down

glm::vec2 screenPos;
screenPos.x = (_clipCoords.x / _clipCoords.w) + 1;
screenPos.y = 1 - (_clipCoords.y / _clipCoords.w);

return screenPos * halfScreen;
}

glm::vec2 worldToScreenSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition, const glm::vec2& _screenSize) {
return clipToScreenSpace(worldToClipSpace(_mvp, _worldPosition), _screenSize);
}

glm::vec2 worldToScreenSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition, const glm::vec2& _screenSize, bool& _clipped) {

glm::vec4 clipCoords = worldToClipSpace(_mvp, _worldPosition);

if (clipCoords.w <= 0.0f) {
_clipped = true;
return {};
}

return clipToScreenSpace(clipCoords, _screenSize);
glm::vec2 worldToScreenSpace(const glm::mat4& mvp, const glm::vec4& worldPosition, const glm::vec2& screenSize, bool& behindCamera) {
glm::vec4 clip = worldToClipSpace(mvp, worldPosition);
glm::vec3 ndc = clipSpaceToNdc(clip);
glm::vec2 screenPosition = ndcToScreenSpace(ndc, screenSize);
behindCamera = clipSpaceIsBehindCamera(clip);
return screenPosition;
}

// square distance from a point <_p> to a segment <_p1,_p2>
Expand Down
24 changes: 16 additions & 8 deletions core/src/util/geom.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,25 @@ float mapValue(const float& _value, const float& _inputMin, const float& _inputM
/* Computes the angle in radians between two points with the axis y = 0 in 2d space */
float angleBetweenPoints(const glm::vec2& _p1, const glm::vec2& _p2);

/* Computes the clip coordinates from position in world space and a model view matrix */
glm::vec4 worldToClipSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition);
/// Computes the clip coordinates from position in world space and a model view matrix
inline glm::vec4 worldToClipSpace(const glm::mat4& mvp, const glm::vec4& worldPosition) {
return mvp * worldPosition;
}

/* Computes the screen coordinates from a coordinate in clip space and a screen size */
glm::vec2 clipToScreenSpace(const glm::vec4& _clipCoords, const glm::vec2& _screenSize);
inline bool clipSpaceIsBehindCamera(const glm::vec4& clip) {
return clip.w < 0;
}

/* Computes the screen coordinates from a world position, a model view matrix and a screen size */
glm::vec2 worldToScreenSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition, const glm::vec2& _screenSize);
glm::vec2 worldToScreenSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition, const glm::vec2& _screenSize, bool& clipped);
inline glm::vec3 clipSpaceToNdc(const glm::vec4& clip) {
return glm::vec3(clip) / clip.w;
}

inline glm::vec2 ndcToScreenSpace(const glm::vec3& ndc, const glm::vec2& screenSize) {
return glm::vec2(1 + ndc.x, 1 - ndc.y) * screenSize * 0.5f;
}

glm::vec2 worldToScreenSpace(const glm::mat4& _mvp, const glm::vec4& _worldPosition, const glm::vec2& _screenSize, bool& _clipped);
/// Computes the screen coordinates from a world position, a model view matrix and a screen size
glm::vec2 worldToScreenSpace(const glm::mat4& mvp, const glm::vec4& worldPosition, const glm::vec2& screenSize, bool& behindCamera);

inline glm::vec2 rotateBy(const glm::vec2& _in, const glm::vec2& _normal) {
return {
Expand Down
18 changes: 14 additions & 4 deletions core/src/view/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,16 +487,26 @@ void View::updateMatrices() {

}

glm::vec2 View::lngLatToScreenPosition(double lng, double lat, bool& clipped) {
glm::vec2 View::lngLatToScreenPosition(double lng, double lat, bool& outsideViewport, bool clipToViewport) {

if (m_dirtyMatrices) { updateMatrices(); } // Need the view matrices to be up-to-date

glm::dvec2 absoluteMeters = MapProjection::lngLatToProjectedMeters({lng, lat});
glm::dvec2 relativeMeters = getRelativeMeters(absoluteMeters);
glm::dvec4 worldPosition(relativeMeters, 0.0, 1.0);

glm::vec2 screenPosition = worldToScreenSpace(m_viewProj, worldPosition, {m_vpWidth, m_vpHeight}, clipped);
glm::vec4 worldPosition(relativeMeters, 0.0, 1.0);
glm::vec4 clip = worldToClipSpace(m_viewProj, worldPosition);
glm::vec3 ndc = clipSpaceToNdc(clip);
outsideViewport = clipSpaceIsBehindCamera(clip) || abs(ndc.x) > 1 || abs(ndc.y) > 1;

if (outsideViewport && clipToViewport) {
// Get direction to the point and determine the point on the screen edge in that direction.
glm::vec4 worldDirection(relativeMeters, 0, 0);
glm::vec4 clipDirection = worldToClipSpace(m_viewProj, worldDirection);
ndc = glm::vec3(clipDirection) / glm::max(abs(clipDirection.x), abs(clipDirection.y));
}

glm::vec2 screenSize(m_vpWidth, m_vpHeight);
glm::vec2 screenPosition = ndcToScreenSpace(ndc, screenSize);
return screenPosition;
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/view/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class View {
const glm::mat4& getProjectionMatrix() const { return m_proj; }

// Get the combined view and projection transformation.
const glm::mat4 getViewProjectionMatrix() const { return m_viewProj; }
const glm::mat4& getViewProjectionMatrix() const { return m_viewProj; }

// Get the normal matrix; transforms surface normals from model space to camera space.
const glm::mat3& getNormalMatrix() const { return m_normalMatrix; }
Expand Down Expand Up @@ -205,7 +205,7 @@ class View {
double screenToGroundPlane(double& _screenX, double& _screenY);

// Get the screen position from a latitude/longitude.
glm::vec2 lngLatToScreenPosition(double lng, double lat, bool& clipped);
glm::vec2 lngLatToScreenPosition(double lng, double lat, bool& outsideViewport, bool clipToViewport = false);

LngLat screenPositionToLngLat(float x, float y, bool& intersection);

Expand Down
4 changes: 2 additions & 2 deletions platforms/android/tangram/src/main/cpp/jniExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,11 @@ jboolean MapController(ScreenPositionToLngLat)(JNIEnv* jniEnv, jobject obj, jlon
}

jboolean MapController(LngLatToScreenPosition)(JNIEnv* jniEnv, jobject obj, jlong mapPtr,
jdoubleArray coordinates) {
jdoubleArray coordinates, jboolean clipToViewport) {
auto_map(mapPtr);

jdouble* arr = jniEnv->GetDoubleArrayElements(coordinates, NULL);
bool result = map->lngLatToScreenPosition(arr[0], arr[1], &arr[0], &arr[1]);
bool result = map->lngLatToScreenPosition(arr[0], arr[1], &arr[0], &arr[1], clipToViewport);
jniEnv->ReleaseDoubleArrayElements(coordinates, arr, 0);
return static_cast<jboolean>(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,10 +646,25 @@ public LngLat screenPositionToLngLat(@NonNull final PointF screenPosition) {
*/
@NonNull
public PointF lngLatToScreenPosition(@NonNull final LngLat lngLat) {
PointF screenPosition = new PointF();
lngLatToScreenPosition(lngLat, screenPosition, false);
return screenPosition;
}

/**
* Find the position on screen corresponding to the given geographic coordinates
* @param lngLat Geographic coordinates.
* @param screenPositionOut Point object to hold result.
* @param clipToViewport If true, results that are outside of the viewport will be clipped to a
* point on the edge of the viewport in the direction towards the location.
* @return True if the resulting point is inside the viewport, otherwise false.
*/
public boolean lngLatToScreenPosition(@NonNull final LngLat lngLat, @NonNull final PointF screenPositionOut, boolean clipToViewport) {
checkPointer(mapPointer);
final double[] tmp = { lngLat.longitude, lngLat.latitude };
nativeLngLatToScreenPosition(mapPointer, tmp);
return new PointF((float)tmp[0], (float)tmp[1]);
boolean insideViewport = nativeLngLatToScreenPosition(mapPointer, tmp, clipToViewport);
screenPositionOut.set((float)tmp[0], (float)tmp[1]);
return insideViewport;
}

/**
Expand Down Expand Up @@ -1393,7 +1408,7 @@ private synchronized native void nativeUpdateCameraPosition(long mapPtr, int set
private synchronized native void nativeGetEnclosingCameraPosition(long mapPtr, double aLng, double aLat, double bLng, double bLat, int[] buffer, double[] lngLatZoom);
private synchronized native void nativeCancelCameraAnimation(long mapPtr);
private synchronized native boolean nativeScreenPositionToLngLat(long mapPtr, double[] coordinates);
private synchronized native boolean nativeLngLatToScreenPosition(long mapPtr, double[] coordinates);
private synchronized native boolean nativeLngLatToScreenPosition(long mapPtr, double[] coordinates, boolean clipToViewport);
private synchronized native void nativeSetPixelScale(long mapPtr, float scale);
private synchronized native void nativeSetCameraType(long mapPtr, int type);
private synchronized native int nativeGetCameraType(long mapPtr);
Expand Down
49 changes: 42 additions & 7 deletions platforms/common/glfwApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,14 @@ bool show_gui = true;
bool load_async = true;
bool add_point_marker_on_click = false;
bool add_polyline_marker_on_click = false;
bool point_markers_position_clipped = false;

std::vector<Tangram::MarkerID> point_markers;
struct PointMarker {
MarkerID markerId;
LngLat coordinates;
};

std::vector<PointMarker> point_markers;

Tangram::MarkerID polyline_marker = 0;
std::vector<Tangram::LngLat> polyline_marker_coordinates;
Expand Down Expand Up @@ -381,7 +387,7 @@ void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
map->markerSetStylingFromString(marker, markerStylingString.c_str());
}

point_markers.push_back(marker);
point_markers.push_back({ marker, location });
}

if (add_polyline_marker_on_click) {
Expand Down Expand Up @@ -611,13 +617,17 @@ void showSceneGUI() {
void showMarkerGUI() {
if (ImGui::CollapsingHeader("Markers")) {
ImGui::Checkbox("Add point markers on click", &add_point_marker_on_click);
if(ImGui::RadioButton("Use Styling Path", markerUseStylingPath)) { markerUseStylingPath = true; }
ImGui::InputText("Path", &markerStylingPath);
if(ImGui::RadioButton("Use Styling String", !markerUseStylingPath)) { markerUseStylingPath = false; }
ImGui::InputTextMultiline("String", &markerStylingString);
if (ImGui::RadioButton("Use Styling Path", markerUseStylingPath)) { markerUseStylingPath = true; }
if (markerUseStylingPath) {
ImGui::InputText("Path", &markerStylingPath);
}
if (ImGui::RadioButton("Use Styling String", !markerUseStylingPath)) { markerUseStylingPath = false; }
if (!markerUseStylingPath) {
ImGui::InputTextMultiline("String", &markerStylingString);
}
if (ImGui::Button("Clear point markers")) {
for (const auto marker : point_markers) {
map->markerRemove(marker);
map->markerRemove(marker.markerId);
}
point_markers.clear();
}
Expand All @@ -629,6 +639,31 @@ void showMarkerGUI() {
polyline_marker_coordinates.clear();
}
}

ImGui::Checkbox("Point markers use clipped position", &point_markers_position_clipped);
if (point_markers_position_clipped) {
// Move all point markers to "clipped" positions.
for (const auto& marker : point_markers) {
double screenClipped[2];
map->lngLatToScreenPosition(marker.coordinates.longitude, marker.coordinates.latitude, &screenClipped[0], &screenClipped[1], true);
LngLat lngLatClipped;
map->screenPositionToLngLat(screenClipped[0], screenClipped[1], &lngLatClipped.longitude, &lngLatClipped.latitude);
map->markerSetPoint(marker.markerId, lngLatClipped);
}

// Display coordinates for last marker.
if (!point_markers.empty()) {
auto& last_marker = point_markers.back();
double screenPosition[2];
map->lngLatToScreenPosition(last_marker.coordinates.longitude, last_marker.coordinates.latitude, &screenPosition[0], &screenPosition[1]);
float screenPositionFloat[2] = {static_cast<float>(screenPosition[0]), static_cast<float>(screenPosition[1])};
ImGui::InputFloat2("Last Marker Screen", screenPositionFloat, 5, ImGuiInputTextFlags_ReadOnly);
double screenClipped[2];
map->lngLatToScreenPosition(last_marker.coordinates.longitude, last_marker.coordinates.latitude, &screenClipped[0], &screenClipped[1], true);
float screenClippedFloat[2] = {static_cast<float>(screenClipped[0]), static_cast<float>(screenClipped[1])};
ImGui::InputFloat2("Last Marker Clipped", screenClippedFloat, 5, ImGuiInputTextFlags_ReadOnly);
}
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion platforms/ios/framework/src/TGMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,18 @@ TG_EXPORT
Convert a longitude and latitude to a view position.
@param coordinate The geographic coordinate to convert
@param clip If true, results that would be outside the viewport are clipped to a position on the edge of the viewport in the direction of the location.
@return The view position of the input coordinate
*/
- (CGPoint)viewPositionFromCoordinate:(CLLocationCoordinate2D)coordinate;
- (CGPoint)viewPositionFromCoordinate:(CLLocationCoordinate2D)coordinate clipToViewport:(BOOL)clip;

/**
Returns true if the coordinate is within the current map viewport.
@param coordinate The geographic coordinate.
@return True if the coordinate is within the current map viewport, otherwise false.
*/
-(BOOL)viewportContainsCoordinate:(CLLocationCoordinate2D)coordinate;

/**
Convert a position in view coordinates into the longitude and latitude of the corresponding geographic location.
Expand Down
25 changes: 18 additions & 7 deletions platforms/ios/framework/src/TGMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -586,16 +586,27 @@ - (int)loadSceneAsyncFromYAML:(NSString *)yaml relativeToURL:(NSURL *)url withUp

#pragma mark Coordinate Conversions

- (CGPoint)viewPositionFromCoordinate:(CLLocationCoordinate2D)coordinate
- (CGPoint)viewPositionFromCoordinate:(CLLocationCoordinate2D)coordinate clipToViewport:(BOOL)clip
{
if (!self.map) { return CGPointZero; }
if (!self.map) {
return CGPointZero;
}

double screenX, screenY;
bool inViewport = self.map->lngLatToScreenPosition(coordinate.longitude, coordinate.latitude, &screenX, &screenY, clip);
screenX /= self.contentScaleFactor;
screenY /= self.contentScaleFactor;
return CGPointMake(screenX, screenY);
}

double viewPosition[2];
self.map->lngLatToScreenPosition(coordinate.longitude, coordinate.latitude, &viewPosition[0], &viewPosition[1]);
viewPosition[0] /= self.contentScaleFactor;
viewPosition[1] /= self.contentScaleFactor;
- (BOOL)viewportContainsCoordinate:(CLLocationCoordinate2D)coordinate
{
if (!self.map) {
return false;
}

return CGPointMake((CGFloat)viewPosition[0], (CGFloat)viewPosition[1]);
double screenX, screenY;
return self.map->lngLatToScreenPosition(coordinate.longitude, coordinate.latitude, &screenX, &screenY);
}

- (CLLocationCoordinate2D)coordinateFromViewPosition:(CGPoint)viewPosition
Expand Down

0 comments on commit 1671b1b

Please sign in to comment.