20 #include "qgssettings.h" 
   24 #include <QDomDocument> 
   25 #include <Qt3DRender/QCamera> 
   26 #include <Qt3DRender/QObjectPicker> 
   27 #include <Qt3DRender/QPickEvent> 
   34   , mMouseDevice( new 
Qt3DInput::QMouseDevice() )
 
   35   , mKeyboardDevice( new 
Qt3DInput::QKeyboardDevice() )
 
   36   , mMouseHandler( new 
Qt3DInput::QMouseHandler )
 
   37   , mKeyboardHandler( new 
Qt3DInput::QKeyboardHandler )
 
   39   mMouseHandler->setSourceDevice( mMouseDevice );
 
   40   connect( mMouseHandler, &Qt3DInput::QMouseHandler::positionChanged,
 
   41            this, &QgsCameraController::onPositionChanged );
 
   42   connect( mMouseHandler, &Qt3DInput::QMouseHandler::wheel,
 
   43            this, &QgsCameraController::onWheel );
 
   44   connect( mMouseHandler, &Qt3DInput::QMouseHandler::pressed,
 
   45            this, &QgsCameraController::onMousePressed );
 
   46   connect( mMouseHandler, &Qt3DInput::QMouseHandler::released,
 
   47            this, &QgsCameraController::onMouseReleased );
 
   48   addComponent( mMouseHandler );
 
   50   mKeyboardHandler->setSourceDevice( mKeyboardDevice );
 
   51   connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::pressed,
 
   52            this, &QgsCameraController::onKeyPressed );
 
   53   connect( mKeyboardHandler, &Qt3DInput::QKeyboardHandler::released,
 
   54            this, &QgsCameraController::onKeyReleased );
 
   55   addComponent( mKeyboardHandler );
 
   58   connect( 
this, &Qt3DCore::QEntity::enabledChanged,
 
   59            mMouseHandler, &Qt3DInput::QMouseHandler::setEnabled );
 
   60   connect( 
this, &Qt3DCore::QEntity::enabledChanged,
 
   61            mKeyboardHandler, &Qt3DInput::QMouseHandler::setEnabled );
 
   63   mFpsNavTimer = 
new QTimer( 
this );
 
   64   mFpsNavTimer->setInterval( 10 );
 
   65   connect( mFpsNavTimer, &QTimer::timeout, 
this, &QgsCameraController::applyFlyModeKeyMovements );
 
   66   mFpsNavTimer->start();
 
   71   if ( navigationMode == mCameraNavigationMode )
 
   74   mCameraNavigationMode = navigationMode;
 
   75   mIgnoreNextMouseMove = 
true;
 
   80   if ( movementSpeed == mCameraMovementSpeed )
 
   83   mCameraMovementSpeed = movementSpeed;
 
   89   mVerticalAxisInversion = inversion;
 
   97   connect( te->terrainPicker(), &Qt3DRender::QObjectPicker::pressed, 
this, &QgsCameraController::onPickerMousePressed );
 
  123 static QVector3D unproject( QVector3D v, 
const QMatrix4x4 &modelView, 
const QMatrix4x4 &projection, QRect viewport )
 
  130   QMatrix4x4 inverse = QMatrix4x4( projection * modelView ).inverted();
 
  132   QVector4D tmp( v, 1.0f );
 
  133   tmp.setX( ( tmp.x() - 
float( viewport.x() ) ) / 
float( viewport.width() ) );
 
  134   tmp.setY( ( tmp.y() - 
float( viewport.y() ) ) / 
float( viewport.height() ) );
 
  135   tmp = tmp * 2.0f - QVector4D( 1.0f, 1.0f, 1.0f, 1.0f );
 
  137   QVector4D obj = inverse * tmp;
 
  141   return obj.toVector3D();
 
  149   float k = ( y - y0 ) / d_y; 
 
  156   QVector3D l0 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 0 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
 
  157   QVector3D l1 = unproject( QVector3D( pt.x(), viewport.height() - pt.y(), 1 ), camera->viewMatrix(), camera->projectionMatrix(), viewport );
 
  159   QVector3D p0( 0, y, 0 ); 
 
  160   QVector3D n( 0, 1, 0 ); 
 
  161   QVector3D l = l1 - l0; 
 
  162   float d = QVector3D::dotProduct( p0 - l0, n ) / QVector3D::dotProduct( l, n );
 
  163   QVector3D p = d * l + l0;
 
  165   return QPointF( p.x(), p.z() );
 
  169 void QgsCameraController::rotateCamera( 
float diffPitch, 
float diffYaw )
 
  174   if ( 
pitch + diffPitch > 180 )
 
  175     diffPitch = 180 - 
pitch;  
 
  176   if ( 
pitch + diffPitch < 0 )
 
  177     diffPitch = 0 - 
pitch;   
 
  184   QQuaternion q = QQuaternion::fromEulerAngles( 
pitch + diffPitch, 
yaw + diffYaw, 0 ) *
 
  185                   QQuaternion::fromEulerAngles( 
pitch, 
yaw, 0 ).conjugated();
 
  188   QVector3D position = mCamera->position();
 
  189   QVector3D viewCenter = mCamera->viewCenter();
 
  190   QVector3D viewVector = viewCenter - position;
 
  191   QVector3D cameraToCenter = q * viewVector;
 
  192   viewCenter = position + cameraToCenter;
 
  213   if ( mTerrainEntity )
 
  221   mCamera->setNearPlane( 
distance / 2 );
 
  222   mCamera->setFarPlane( 
distance * 2 );
 
  244   if ( camPose == mCameraPose )
 
  247   mCameraPose = camPose;
 
  257   QDomElement elemCamera = doc.createElement( QStringLiteral( 
"camera" ) );
 
  258   elemCamera.setAttribute( QStringLiteral( 
"x" ), mCameraPose.
centerPoint().
x() );
 
  259   elemCamera.setAttribute( QStringLiteral( 
"y" ), mCameraPose.
centerPoint().
z() );
 
  260   elemCamera.setAttribute( QStringLiteral( 
"elev" ), mCameraPose.
centerPoint().
y() );
 
  262   elemCamera.setAttribute( QStringLiteral( 
"pitch" ), mCameraPose.
pitchAngle() );
 
  263   elemCamera.setAttribute( QStringLiteral( 
"yaw" ), mCameraPose.
headingAngle() );
 
  269   float x = elem.attribute( QStringLiteral( 
"x" ) ).toFloat();
 
  270   float y = elem.attribute( QStringLiteral( 
"y" ) ).toFloat();
 
  271   float elev = elem.attribute( QStringLiteral( 
"elev" ) ).toFloat();
 
  272   float dist = elem.attribute( QStringLiteral( 
"dist" ) ).toFloat();
 
  273   float pitch = elem.attribute( QStringLiteral( 
"pitch" ) ).toFloat();
 
  274   float yaw = elem.attribute( QStringLiteral( 
"yaw" ) ).toFloat();
 
  278 void QgsCameraController::updateCameraFromPose( 
bool centerPointChanged )
 
  283     qWarning() << 
"camera position got NaN!";
 
  297   if ( mCamera && mTerrainEntity && centerPointChanged )
 
  301     QVector3D intersectionPoint;
 
  302     QgsRayCastingUtils::Ray3D ray = QgsRayCastingUtils::rayForCameraCenter( mCamera );
 
  303     if ( mTerrainEntity->rayIntersection( ray, intersectionPoint ) )
 
  305       float dist = ( intersectionPoint - mCamera->position() ).length();
 
  313       centerPoint.
set( centerPoint.
x(), mTerrainEntity->terrainElevationOffset(), centerPoint.
z() );
 
  321 void QgsCameraController::moveCameraPositionBy( 
const QVector3D &posDiff )
 
  339 void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
 
  341   switch ( mCameraNavigationMode )
 
  344       onPositionChangedTerrainNavigation( mouse );
 
  348       onPositionChangedFlyNavigation( mouse );
 
  353 void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseEvent *mouse )
 
  355   if ( mIgnoreNextMouseMove )
 
  357     mIgnoreNextMouseMove = 
false;
 
  358     mMousePos = QPoint( mouse->x(), mouse->y() );
 
  362   int dx = mouse->x() - mMousePos.x();
 
  363   int dy = mouse->y() - mMousePos.y();
 
  365   bool hasShift = ( mouse->modifiers() & Qt::ShiftModifier );
 
  366   bool hasCtrl = ( mouse->modifiers() & Qt::ControlModifier );
 
  367   bool hasLeftButton = ( mouse->buttons() & Qt::LeftButton );
 
  368   bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
 
  369   bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
 
  371   if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
 
  380     updateCameraFromPose();
 
  382   else if ( hasLeftButton && hasCtrl && !hasShift )
 
  385     float diffPitch = 0.2f * dy;
 
  386     float diffYaw = - 0.2f * dx;
 
  387     rotateCamera( diffPitch, diffYaw );
 
  388     updateCameraFromPose( 
true );
 
  390   else if ( hasLeftButton && !hasShift && !hasCtrl )
 
  396     float z = mLastPressedHeight;
 
  401     center.
set( center.
x() - ( p2.x() - p1.x() ), center.
y(), center.
z() - ( p2.y() - p1.y() ) );
 
  403     updateCameraFromPose( 
true );
 
  405   else if ( hasRightButton && !hasShift && !hasCtrl )
 
  410   mMousePos = QPoint( mouse->x(), mouse->y() );
 
  419   dist -= dist * factor * 0.01f;
 
  421   updateCameraFromPose();
 
  424 void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
 
  426   switch ( mCameraNavigationMode )
 
  430       float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
 
  431       setCameraMovementSpeed( mCameraMovementSpeed + mCameraMovementSpeed * scaling * wheel->angleDelta().y() );
 
  437       float scaling = ( ( wheel->modifiers() & Qt::ControlModifier ) ? 0.1f : 1.0f ) / 1000.f;
 
  439       dist -= dist * scaling * wheel->angleDelta().y();
 
  441       updateCameraFromPose();
 
  447 void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
 
  450   mKeyboardHandler->setFocus( 
true );
 
  451   if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton || mouse->button() == Qt3DInput::QMouseEvent::MiddleButton )
 
  453     mMousePos = QPoint( mouse->x(), mouse->y() );
 
  454     mPressedButton = mouse->button();
 
  455     mMousePressed = 
true;
 
  456     if ( mCaptureFpsMouseMovements )
 
  457       mIgnoreNextMouseMove = 
true;
 
  461 void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
 
  464   mPressedButton = Qt3DInput::QMouseEvent::NoButton;
 
  465   mMousePressed = 
false;
 
  468 void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
 
  470   if ( event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_QuoteLeft )
 
  473     switch ( mCameraNavigationMode )
 
  475       case NavigationMode::WalkNavigation:
 
  478       case NavigationMode::TerrainBasedNavigation:
 
  486   switch ( mCameraNavigationMode )
 
  490       onKeyPressedFlyNavigation( event );
 
  496       onKeyPressedTerrainNavigation( event );
 
  502 void QgsCameraController::onKeyPressedTerrainNavigation( Qt3DInput::QKeyEvent *event )
 
  504   const bool hasShift = ( 
event->modifiers() & Qt::ShiftModifier );
 
  505   const bool hasCtrl = ( 
event->modifiers() & Qt::ControlModifier );
 
  507   int tx = 0, ty = 0, tElev = 0;
 
  508   switch ( event->key() )
 
  524     case Qt::Key_PageDown:
 
  534     if ( !hasShift && !hasCtrl )
 
  538     else if ( hasShift && !hasCtrl )
 
  544     else if ( hasCtrl && !hasShift )
 
  547       float diffPitch = ty;   
 
  549       rotateCamera( diffPitch, diffYaw );
 
  550       updateCameraFromPose( 
true );
 
  557     center.
set( center.
x(), center.
y() + tElev * 10, center.
z() );
 
  559     updateCameraFromPose( 
true );
 
  563 void QgsCameraController::onKeyPressedFlyNavigation( Qt3DInput::QKeyEvent *event )
 
  565   switch ( event->key() )
 
  567     case Qt::Key_QuoteLeft:
 
  570       mCaptureFpsMouseMovements = !mCaptureFpsMouseMovements;
 
  571       mIgnoreNextMouseMove = 
true;
 
  572       if ( mCaptureFpsMouseMovements )
 
  574         qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );
 
  578         qApp->restoreOverrideCursor();
 
  586       if ( mCaptureFpsMouseMovements )
 
  588         mCaptureFpsMouseMovements = 
false;
 
  589         mIgnoreNextMouseMove = 
true;
 
  590         qApp->restoreOverrideCursor();
 
  600   if ( event->isAutoRepeat() )
 
  603   mDepressedKeys.insert( event->key() );
 
  606 void QgsCameraController::applyFlyModeKeyMovements()
 
  608   QVector3D cameraUp = mCamera->upVector().normalized();
 
  610   QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
 
  612   QVector3D cameraPosDiff( 0.0f, 0.0f, 0.0f );
 
  615   const bool shiftPressed = mDepressedKeys.contains( Qt::Key_Shift );
 
  616   const bool ctrlPressed = mDepressedKeys.contains( Qt::Key_Control );
 
  618   double movementSpeed = mCameraMovementSpeed * ( shiftPressed ? 2 : 1 ) * ( ctrlPressed ? 0.1 : 1 );
 
  620   bool changed = 
false;
 
  621   if ( mDepressedKeys.contains( Qt::Key_Left ) || mDepressedKeys.contains( Qt::Key_A ) )
 
  624     cameraPosDiff += movementSpeed * cameraLeft;
 
  627   if ( mDepressedKeys.contains( Qt::Key_Right ) || mDepressedKeys.contains( Qt::Key_D ) )
 
  630     cameraPosDiff += - movementSpeed * cameraLeft;
 
  633   if ( mDepressedKeys.contains( Qt::Key_Up ) || mDepressedKeys.contains( Qt::Key_W ) )
 
  636     cameraPosDiff += movementSpeed * cameraFront;
 
  639   if ( mDepressedKeys.contains( Qt::Key_Down ) || mDepressedKeys.contains( Qt::Key_S ) )
 
  642     cameraPosDiff += - movementSpeed * cameraFront;
 
  647   static constexpr 
double ELEVATION_MOVEMENT_SCALE = 0.5;
 
  648   if ( mDepressedKeys.contains( Qt::Key_PageUp ) || mDepressedKeys.contains( Qt::Key_E ) )
 
  651     cameraPosDiff += ELEVATION_MOVEMENT_SCALE * movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
 
  654   if ( mDepressedKeys.contains( Qt::Key_PageDown ) || mDepressedKeys.contains( Qt::Key_Q ) )
 
  657     cameraPosDiff += ELEVATION_MOVEMENT_SCALE * - movementSpeed * QVector3D( 0.0f, 1.0f, 0.0f );
 
  661     moveCameraPositionBy( cameraPosDiff );
 
  664 void QgsCameraController::onPositionChangedFlyNavigation( Qt3DInput::QMouseEvent *mouse )
 
  666   const bool hasMiddleButton = ( mouse->buttons() & Qt::MiddleButton );
 
  667   const bool hasRightButton = ( mouse->buttons() & Qt::RightButton );
 
  669   const double dx = mCaptureFpsMouseMovements ? QCursor::pos().x() - mMousePos.x() : mouse->x() - mMousePos.x();
 
  670   const double dy = mCaptureFpsMouseMovements ? QCursor::pos().y() - mMousePos.y() : mouse->y() - mMousePos.y();
 
  671   mMousePos = mCaptureFpsMouseMovements ? QCursor::pos() : QPoint( mouse->x(), mouse->y() );
 
  673   if ( mIgnoreNextMouseMove )
 
  675     mIgnoreNextMouseMove = 
false;
 
  679   if ( hasMiddleButton )
 
  682     QVector3D cameraUp = mCamera->upVector().normalized();
 
  684     QVector3D cameraLeft = QVector3D::crossProduct( cameraUp, cameraFront );
 
  685     QVector3D cameraPosDiff = -dx * cameraLeft - dy * cameraUp;
 
  686     moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 10.0 );
 
  688   else if ( hasRightButton )
 
  692     QVector3D cameraPosDiff = dy * cameraFront;
 
  693     moveCameraPositionBy( mCameraMovementSpeed * cameraPosDiff / 5.0 );
 
  697     if ( mCaptureFpsMouseMovements )
 
  699       float diffPitch = -0.2f * dy;
 
  700       switch ( mVerticalAxisInversion )
 
  711       float diffYaw = - 0.2f * dx;
 
  712       rotateCamera( diffPitch, diffYaw );
 
  713       updateCameraFromPose( 
false );
 
  715     else if ( mouse->buttons() & Qt::LeftButton )
 
  717       float diffPitch = -0.2f * dy;
 
  718       switch ( mVerticalAxisInversion )
 
  728       float diffYaw = - 0.2f * dx;
 
  729       rotateCamera( diffPitch, diffYaw );
 
  730       updateCameraFromPose( 
false );
 
  734   if ( mCaptureFpsMouseMovements )
 
  736     mIgnoreNextMouseMove = 
true;
 
  739     emit 
setCursorPosition( QPoint( mViewport.width() / 2, mViewport.height() / 2 ) );
 
  743 void QgsCameraController::onKeyReleased( Qt3DInput::QKeyEvent *event )
 
  745   if ( event->isAutoRepeat() )
 
  748   mDepressedKeys.remove( event->key() );
 
  751 void QgsCameraController::onPickerMousePressed( Qt3DRender::QPickEvent *pick )
 
  753   mLastPressedHeight = pick->worldIntersection().y();
 
  763   updateCameraFromPose();
 
  772   updateCameraFromPose();
 
  773   qInfo() << 
"Delta yaw: " << deltaYaw;
 
  774   qInfo() << 
"Yaw: " << 
yaw;
 
  780   updateCameraFromPose();
 
  787   float x = tx * dist * 0.02f;
 
  788   float y = -ty * dist * 0.02f;
 
  791   float t = sqrt( x * x + y * y );
 
  792   float a = atan2( y, x ) - 
yaw * M_PI / 180;
 
  793   float dx = cos( a ) * t;
 
  794   float dy = sin( a ) * t;
 
  797   center.
set( center.
x() + dx, center.
y(), center.
z() + dy );
 
  799   updateCameraFromPose( 
true );
 
  804   if ( event->key() == Qt::Key_QuoteLeft )
 
  807   switch ( mCameraNavigationMode )
 
  811       switch ( event->key() )
 
  823         case Qt::Key_PageDown:
 
  828           if ( mCaptureFpsMouseMovements )
 
  840       switch ( event->key() )
 
  845         case Qt::Key_PageDown:
 
void setViewport(QRect viewport)
Sets viewport rectangle. Called internally from 3D canvas. Allows conversion of mouse coordinates.
void readXml(const QDomElement &elem)
Reads camera configuration from the given DOM element.
float pitch() const
Returns pitch angle in degrees (0 = looking from the top, 90 = looking from the side).
float yaw() const
Returns yaw angle in degrees.
void setCameraNavigationMode(QgsCameraController::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
void tiltUpAroundViewCenter(float deltaPitch)
Tilt up the view by deltaPitch around the view center (camera moves)
bool willHandleKeyEvent(QKeyEvent *event)
Returns true if the camera controller will handle the specified key event, preventing it from being i...
void navigationModeHotKeyPressed(QgsCameraController::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
Qt3DRender::QCamera * camera
void setVerticalAxisInversion(QgsCameraController::VerticalAxisInversion inversion)
Sets the vertical axis inversion behavior.
float distance() const
Returns distance of the camera from the point it is looking at.
void setCamera(Qt3DRender::QCamera *camera)
Assigns camera that should be controlled by this class. Called internally from 3D scene.
VerticalAxisInversion
Vertical axis inversion options.
@ WhenDragging
Invert vertical axis movements when dragging in first person modes.
@ Always
Always invert vertical axis movements.
@ Never
Never invert vertical axis movements.
void cameraChanged()
Emitted when camera has been updated.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
void frameTriggered(float dt)
Called internally from 3D scene when a new frame is generated. Updates camera according to keyboard/m...
void resetView(float distance)
Move camera back to the initial position (looking down towards origin of world's coordinates)
void setLookingAtPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets the complete camera configuration: the point towards it is looking (in 3D world coordinates),...
QgsVector3D lookingAtPoint() const
Returns the point in the world coordinates towards which the camera is looking.
QDomElement writeXml(QDomDocument &doc) const
Writes camera configuration to the given DOM element.
void setViewFromTop(float worldX, float worldY, float distance, float yaw=0)
Sets camera to look down towards given point in world coordinate, in given distance from plane with z...
NavigationMode
The navigation mode used by the camera.
@ WalkNavigation
Uses WASD keys or arrows to navigate in walking (first person) manner.
@ TerrainBasedNavigation
The default navigation based on the terrain.
void zoom(float factor)
Zoom the map by factor.
void setTerrainEntity(QgsTerrainEntity *te)
Connects to object picker attached to terrain entity.
void rotateAroundViewCenter(float deltaYaw)
Rotate clockwise the view by deltaYaw around the view center (camera moves)
QgsCameraController(Qt3DCore::QNode *parent=nullptr)
Constructs the camera controller with optional parent node that will take ownership.
void setCameraHeadingAngle(float angle)
Set camera heading to angle (used for rotating the view)
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
void setCameraPose(const QgsCameraPose &camPose)
Sets camera pose.
void viewportChanged()
Emitted when viewport rectangle has been updated.
void setCursorPosition(QPoint point)
Emitted when the mouse cursor position should be moved to the specified point on the map viewport.
void moveView(float tx, float ty)
Move the map by tx and ty.
float headingAngle() const
Returns heading (yaw) angle in degrees.
QgsVector3D centerPoint() const
Returns center point (towards which point the camera is looking)
float pitchAngle() const
Returns pitch angle in degrees.
float distanceFromCenterPoint() const
Returns distance of the camera from the center point.
void setPitchAngle(float pitch)
Sets pitch angle in degrees.
void setCenterPoint(const QgsVector3D &point)
Sets center point (towards which point the camera is looking)
void setHeadingAngle(float heading)
Sets heading (yaw) angle in degrees.
void setDistanceFromCenterPoint(float distance)
Sets distance of the camera from the center point.
void updateCamera(Qt3DRender::QCamera *camera)
Update Qt3D camera view matrix based on the pose.
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
void set(double x, double y, double z)
Sets vector coordinates.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
QPointF screen_point_to_point_on_plane(QPointF pt, QRect viewport, Qt3DRender::QCamera *camera, float y)
float find_x_on_line(float x0, float y0, float x1, float y1, float y)