/** * @file llviewercamera.cpp * @brief LLViewerCamera class implementation * * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. * $License$ */ #include // for setprecision #include "llviewerprecompiledheaders.h" #include "llviewercamera.h" #include "llquaternion.h" #include "llagent.h" #include "llviewercontrol.h" #include "lldrawable.h" #include "llface.h" #include "llgl.h" #include "llglheaders.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewerwindow.h" #include "llvovolume.h" #include "llworld.h" BOOL LLViewerCamera::sDisableCameraConstraints = FALSE; LLViewerCamera *gCamera = NULL; LLViewerCamera::LLViewerCamera() : LLCamera() { calcProjection(getFar()); S32 i; for (i = 0; i < 16; i++) { mGLProjectionMatrix[i] = 0.f; } mCameraFOVDefault = DEFAULT_FIELD_OF_VIEW; mPixelMeterRatio = 0.f; mScreenPixelArea = 0; mZoomFactor = 1.f; mZoomSubregion = 1; } void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVector3 &up_direction, const LLVector3 &point_of_interest) { LLVector3 last_position; LLVector3 last_axis; last_position = getOrigin(); last_axis = getAtAxis(); mLastPointOfInterest = point_of_interest; // constrain to max distance from avatar LLVector3 camera_offset = center - gAgent.getPositionAgent(); setOriginAndLookAt(center, up_direction, point_of_interest); F32 dpos = (center - last_position).magVec(); LLQuaternion rotation; rotation.shortestArc(last_axis, getAtAxis()); F32 x, y, z; F32 drot; rotation.getAngleAxis(&drot, &x, &y, &z); mVelocityStat.addValue(dpos); mAngularVelocityStat.addValue(drot); // update pixel meter ratio using default fov, not modified one mPixelMeterRatio = mViewHeightInPixels / (2.f*tanf(mCameraFOVDefault*0.5)); // update screen pixel area mScreenPixelArea =(S32)((F32)mViewHeightInPixels * ((F32)mViewHeightInPixels * mAspect)); } // Handy copies of last good GL matrices F64 gGLModelView[16]; S32 gGLViewport[4]; const LLMatrix4 &LLViewerCamera::getProjection() const { calcProjection(getFar()); return mProjectionMatrix; } const LLMatrix4 &LLViewerCamera::getModelview() const { LLMatrix4 cfr(OGL_TO_CFR_ROTATION); getMatrixToLocal(mModelviewMatrix); mModelviewMatrix *= cfr; return mModelviewMatrix; } void LLViewerCamera::calcProjection(const F32 far_distance) const { F32 fov_y, z_far, z_near, aspect, f; fov_y = getView(); z_far = far_distance; z_near = getNear(); aspect = getAspect(); f = 1/tan(fov_y*0.5f); mProjectionMatrix.zero(); mProjectionMatrix.mMatrix[0][0] = f/aspect; mProjectionMatrix.mMatrix[1][1] = f; mProjectionMatrix.mMatrix[2][2] = (z_far + z_near)/(z_near - z_far); mProjectionMatrix.mMatrix[3][2] = (2*z_far*z_near)/(z_near - z_far); mProjectionMatrix.mMatrix[2][3] = -1; } // Sets up opengl state for 3D drawing. If for selection, also // sets up a pick matrix. x and y are ignored if for_selection is false. // The picking region is centered on x,y and has the specified width and // height. LLMatrix4 gProjectionMat; void LLViewerCamera::updateFrustumPlanes() { GLint viewport[4]; GLdouble model[16]; GLdouble proj[16]; GLdouble objX,objY,objZ; LLVector3 frust[8]; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, model); glGetDoublev(GL_PROJECTION_MATRIX,proj); gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); /*gluUnProject(viewport[0],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); frust[4].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0]+viewport[2],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); frust[5].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); frust[6].setVec((F32)objX,(F32)objY,(F32)objZ); gluUnProject(viewport[0],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); frust[7].setVec((F32)objX,(F32)objY,(F32)objZ);*/ for (U32 i = 0; i < 4; i++) { LLVector3 vec = frust[i] - getOrigin(); vec.normVec(); frust[i+4] = getOrigin() + vec*getFar()*2.0; } calcAgentFrustumPlanes(frust); } void LLViewerCamera::setPerspective(BOOL for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, BOOL limit_select_distance, F32 z_near, F32 z_far) { F32 fov_y, aspect; fov_y = RAD_TO_DEG * getView(); BOOL z_default_near, z_default_far = FALSE; if (z_far <= 0) { z_default_far = TRUE; z_far = getFar(); } if (z_near <= 0) { z_default_near = TRUE; z_near = getNear(); } aspect = getAspect(); // Load camera view matrix glMatrixMode( GL_PROJECTION ); glLoadIdentity(); if (for_selection) { // make a tiny little viewport // anything drawn into this viewport will be "selected" const U8 VIEWPORT_VECTOR_LEN = 4; GLint viewport[VIEWPORT_VECTOR_LEN]; glGetIntegerv(GL_VIEWPORT, viewport); gluPickMatrix(x + width / 2, y_from_bot + height / 2, width, height, viewport); if (limit_select_distance) { // ...select distance from control z_far = gSavedSettings.getF32("MaxSelectDistance"); } else { z_far = gAgent.mDrawDistance; } } else { // Only override the far clip if it's not passed in explicitly. if (z_default_far) { z_far = MAX_FAR_CLIP; } glViewport(x, y_from_bot, width, height); } if (mZoomFactor > 1.f) { float offset = mZoomFactor - 1.f; int pos_y = mZoomSubregion / llceil(mZoomFactor); int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); glTranslatef(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f); glScalef(mZoomFactor, mZoomFactor, 1.f); } calcProjection(z_far); // Update the projection matrix cache gluPerspective(fov_y, aspect, z_near, z_far); glGetFloatv(GL_PROJECTION_MATRIX, (float*)&gProjectionMat); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame GLfloat ogl_matrix[16]; getOpenGLTransform(ogl_matrix); glMultMatrixf(ogl_matrix); if (for_selection && (width > 1 || height > 1)) { calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidth() - 0.5f, (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeight() - 0.5f, (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidth() - 0.5f, (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeight() - 0.5f); } // if not picking and not doing a snapshot, cache various GL matrices if (!for_selection && mZoomFactor == 1.f) { // Save GL matrices for access elsewhere in code, especially project_world_to_screen glGetDoublev(GL_PROJECTION_MATRIX, mGLProjectionMatrix); glGetDoublev(GL_MODELVIEW_MATRIX, gGLModelView); glGetIntegerv(GL_VIEWPORT, (GLint*)gGLViewport); } updateFrustumPlanes(); if (gSavedSettings.getBOOL("CameraOffset")) { glMatrixMode(GL_PROJECTION); glTranslatef(0,0,-50); glRotatef(20.0,1,0,0); glMatrixMode(GL_MODELVIEW); } } // Uses the last GL matrices set in set_perspective to project a point from // screen coordinates to the agent's region. void LLViewerCamera::projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent) const { GLdouble x, y, z; gluUnProject( GLdouble(screen_x), GLdouble(screen_y), 0.0, gGLModelView, mGLProjectionMatrix, (GLint*)gGLViewport, &x, &y, &z ); pos_agent->setVec( (F32)x, (F32)y, (F32)z ); } // Uses the last GL matrices set in set_perspective to project a point from // the agent's region space to screen coordinates. Returns TRUE if point in within // the current window. BOOL LLViewerCamera::projectPosAgentToScreen(const LLVector3 &pos_agent, LLCoordGL &out_point, const BOOL clamp) const { BOOL in_front = TRUE; GLdouble x, y, z; // object's window coords, GL-style LLVector3 dir_to_point = pos_agent - getOrigin(); dir_to_point /= dir_to_point.magVec(); if (dir_to_point * getAtAxis() < 0.f) { if (clamp) { return FALSE; } else { in_front = FALSE; } } if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ], gGLModelView, mGLProjectionMatrix, (GLint*)gGLViewport, &x, &y, &z)) { // convert screen coordinates to virtual UI coordinates x /= gViewerWindow->getDisplayScale().mV[VX]; y /= gViewerWindow->getDisplayScale().mV[VY]; // should now have the x,y coords of grab_point in screen space const LLRect& window_rect = gViewerWindow->getWindowRect(); // ...sanity check S32 int_x = lltrunc(x); S32 int_y = lltrunc(y); BOOL valid = TRUE; if (clamp) { if (int_x < window_rect.mLeft) { out_point.mX = window_rect.mLeft; valid = FALSE; } else if (int_x > window_rect.mRight) { out_point.mX = window_rect.mRight; valid = FALSE; } else { out_point.mX = int_x; } if (int_y < window_rect.mBottom) { out_point.mY = window_rect.mBottom; valid = FALSE; } else if (int_y > window_rect.mTop) { out_point.mY = window_rect.mTop; valid = FALSE; } else { out_point.mY = int_y; } return valid; } else { out_point.mX = int_x; out_point.mY = int_y; if (int_x < window_rect.mLeft) { valid = FALSE; } else if (int_x > window_rect.mRight) { valid = FALSE; } if (int_y < window_rect.mBottom) { valid = FALSE; } else if (int_y > window_rect.mTop) { valid = FALSE; } return in_front && valid; } } else { return FALSE; } } // Uses the last GL matrices set in set_perspective to project a point from // the agent's region space to the nearest edge in screen coordinates. // Returns TRUE if projection succeeds. BOOL LLViewerCamera::projectPosAgentToScreenEdge(const LLVector3 &pos_agent, LLCoordGL &out_point) const { LLVector3 dir_to_point = pos_agent - getOrigin(); dir_to_point /= dir_to_point.magVec(); BOOL in_front = TRUE; if (dir_to_point * getAtAxis() < 0.f) { in_front = FALSE; } GLdouble x, y, z; // object's window coords, GL-style if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ], gGLModelView, mGLProjectionMatrix, (GLint*)gGLViewport, &x, &y, &z)) { x /= gViewerWindow->getDisplayScale().mV[VX]; y /= gViewerWindow->getDisplayScale().mV[VY]; // should now have the x,y coords of grab_point in screen space const LLRect& window_rect = gViewerWindow->getVirtualWindowRect(); // ...sanity check S32 int_x = lltrunc(x); S32 int_y = lltrunc(y); // find the center GLdouble center_x = (GLdouble)(0.5f * (window_rect.mLeft + window_rect.mRight)); GLdouble center_y = (GLdouble)(0.5f * (window_rect.mBottom + window_rect.mTop)); if (x == center_x && y == center_y) { // can't project to edge from exact center return FALSE; } // find the line from center to local GLdouble line_x = x - center_x; GLdouble line_y = y - center_y; int_x = lltrunc(center_x); int_y = lltrunc(center_y); if (0.f == line_x) { // the slope of the line is undefined if (line_y > 0.f) { int_y = window_rect.mTop; } else { int_y = window_rect.mBottom; } } else if (0 == window_rect.getWidth()) { // the diagonal slope of the view is undefined if (y < window_rect.mBottom) { int_y = window_rect.mBottom; } else if ( y > window_rect.mTop) { int_y = window_rect.mTop; } } else { F32 line_slope = (F32)(line_y / line_x); F32 rect_slope = ((F32)window_rect.getHeight()) / ((F32)window_rect.getWidth()); if (fabs(line_slope) > rect_slope) { if (line_y < 0.f) { // bottom int_y = window_rect.mBottom; } else { // top int_y = window_rect.mTop; } int_x = lltrunc(((GLdouble)int_y - center_y) / line_slope + center_x); } else if (fabs(line_slope) < rect_slope) { if (line_x < 0.f) { // left int_x = window_rect.mLeft; } else { // right int_x = window_rect.mRight; } int_y = lltrunc(((GLdouble)int_x - center_x) * line_slope + center_y); } else { // exactly parallel ==> push to the corners if (line_x > 0.f) { int_x = window_rect.mRight; } else { int_x = window_rect.mLeft; } if (line_y > 0.0f) { int_y = window_rect.mTop; } else { int_y = window_rect.mBottom; } } } if (!in_front) { int_x = window_rect.mLeft + window_rect.mRight - int_x; int_y = window_rect.mBottom + window_rect.mTop - int_y; } out_point.mX = int_x; out_point.mY = int_y; return TRUE; } return FALSE; } void LLViewerCamera::getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right) { LLVector3 to_vec = pos_agent - getOrigin(); F32 at_dist = to_vec * getAtAxis(); F32 height_meters = at_dist* (F32)tan(getView()/2.f); F32 height_pixels = getViewHeightInPixels()/2.f; F32 pixel_aspect = gViewerWindow->getWindow()->getPixelAspectRatio(); F32 meters_per_pixel = height_meters / height_pixels; up = getUpAxis() * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; right = -1.f * pixel_aspect * meters_per_pixel * getLeftAxis() * gViewerWindow->getDisplayScale().mV[VX]; } LLVector3 LLViewerCamera::roundToPixel(const LLVector3 &pos_agent) { F32 dist = (pos_agent - getOrigin()).magVec(); // Convert to screen space and back, preserving the depth. LLCoordGL screen_point; if (!projectPosAgentToScreen(pos_agent, screen_point, FALSE)) { // Off the screen, just return the original position. return pos_agent; } LLVector3 ray_dir; projectScreenToPosAgent(screen_point.mX, screen_point.mY, &ray_dir); ray_dir -= getOrigin(); ray_dir.normVec(); LLVector3 pos_agent_rounded = getOrigin() + ray_dir*dist; /* LLVector3 pixel_x, pixel_y; getPixelVectors(pos_agent_rounded, pixel_y, pixel_x); pos_agent_rounded += 0.5f*pixel_x, 0.5f*pixel_y; */ return pos_agent_rounded; } BOOL LLViewerCamera::cameraUnderWater() const { return getOrigin().mV[VZ] < gAgent.getRegion()->getWaterHeight(); } BOOL LLViewerCamera::areVertsVisible(LLViewerObject* volumep, BOOL all_verts) { S32 i, num_faces; LLDrawable* drawablep = volumep->mDrawable; if (!drawablep) { return FALSE; } num_faces = drawablep->getNumFaces(); for (i = 0; i < num_faces; i++) { LLStrider vertices; LLFace* face = drawablep->getFace(i); face->getVertices(vertices); for (S32 v = 0; v < (S32)drawablep->getFace(i)->getGeomCount(); v++) { LLVector3 vec = vertices[v]; if (!face->isState(LLFace::GLOBAL)) { vec = vec*face->getRenderMatrix(); } BOOL in_frustum = pointInFrustum(vec) > 0; if ( !in_frustum && all_verts || in_frustum && !all_verts) { return !all_verts; } } } return all_verts; }