public void SetRotationAboutEye(double x, double y, double z) { msgMatrix matx = new msgMatrix(); msgVectorStruct LookAtPos = new msgVectorStruct(); matx.Rotate(zero_p, x_axe, x / 180.0 * Math.PI); matx.Rotate(zero_p, z_axe, -z / 180.0 * Math.PI); LookAtPos.x = 0.0; LookAtPos.y = m_fFocalLength; LookAtPos.z = 0.0; matx.ApplyMatrixToVector(zero_p, LookAtPos); m_fLookAtPos.x = m_fEyePos.x + LookAtPos.x; m_fLookAtPos.y = m_fEyePos.y + LookAtPos.y; m_fLookAtPos.z = m_fEyePos.z + LookAtPos.z; // Calculate our camera's UpVector using ONLY the 'X' (Pitch) and 'Z' (Yaw) // parameters m_fUpVector = z_axe; matx.ApplyMatrixToVector(zero_p, m_fUpVector); // Just save the 'y' value for later use when calculating the camera's 'Up' vector // prior to calling gluLookAt() m_fRoll = y; }
public void CalculateUpVector() { msgMatrix matx = new msgMatrix(); matx.Rotate(zero_p, x_axe, m_fPitch / 180.0 * Math.PI); matx.Rotate(zero_p, z_axe, -m_fYaw / 180.0 * Math.PI); m_fUpVector = z_axe; matx.ApplyMatrixToVector(zero_p, m_fUpVector); }
public void PositionCamera() { if (m_bResetClippingPlanes) { // Reset our cameras clipping plane ResetView(0, 0); m_bResetClippingPlanes = false; } if (m_fRoll != 0) { msgMatrix matx = new msgMatrix(); msgVectorStruct UpVector; matx.Rotate(zero_p, x_axe, m_fPitch / 180.0 * Math.PI); matx.Rotate(zero_p, y_axe, m_fRoll / 180.0 * Math.PI); matx.Rotate(zero_p, z_axe, -m_fYaw / 180.0 * Math.PI); UpVector = z_axe; matx.ApplyMatrixToVector(zero_p, UpVector); // Position the camera using the newly calculated 'Up' vector OpenGLControl.gluLookAt(m_fEyePos.x, m_fEyePos.y, m_fEyePos.z, m_fLookAtPos.x, m_fLookAtPos.y, m_fLookAtPos.z, UpVector.x, UpVector.y, UpVector.z); } else { // Since our 'Up' vector has already been calculated, all we need to do is // position the camera.. OpenGLControl.gluLookAt(m_fEyePos.x, m_fEyePos.y, m_fEyePos.z, m_fLookAtPos.x, m_fLookAtPos.y, m_fLookAtPos.z, m_fUpVector.x, m_fUpVector.y, m_fUpVector.z); } // Save the Model view matrix. This is used later for // conversion of mouse coordinates to world coordinates. OpenGLControl.glGetDoublev(OpenGLControl.GL_MODELVIEW_MATRIX, m_dModelViewMatrix); }
public void SetRotationAboutLookAt(double x, double y, double z) { msgMatrix matx = new msgMatrix(); msgVectorStruct EyePos = new msgVectorStruct(); matx.Rotate(zero_p, x_axe, x / 180.0 * Math.PI); matx.Rotate(zero_p, z_axe, -z / 180.0 * Math.PI); EyePos.x = 0.0; EyePos.y = -m_fFocalLength; EyePos.z = 0.0; matx.ApplyMatrixToVector(zero_p, EyePos); m_fEyePos.x = EyePos.x + m_fLookAtPos.x; m_fEyePos.y = EyePos.y + m_fLookAtPos.y; m_fEyePos.z = EyePos.z + m_fLookAtPos.z; // Calculate our camera's UpVector using ONLY the 'X' (Pitch) and 'Z' (Yaw) // parameters m_fUpVector = z_axe; matx.ApplyMatrixToVector(zero_p, m_fUpVector); // Just save the 'y' value for later use when calculating the camera's 'Up' vector // prior to calling gluLookAt() m_fRoll = y; }
public void FitBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { BoundingPlane boundsRight = new BoundingPlane(); BoundingPlane boundsLeft = new BoundingPlane(); BoundingPlane boundsTop = new BoundingPlane(); BoundingPlane boundsBottom = new BoundingPlane(); BoundingPlane boundsNear = new BoundingPlane(); BoundingPlane boundsFar = new BoundingPlane(); msgVectorStruct boundsMax = new msgVectorStruct(); msgVectorStruct boundsMin = new msgVectorStruct(); msgVectorStruct vecCenter = new msgVectorStruct(); msgVectorStruct[] vertices = new msgVectorStruct[8]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = new msgVectorStruct(); } msgVectorStruct vecOffset = new msgVectorStruct(); msgMatrix matrix = new msgMatrix(); double focalLength = 0; double fDepthWidth = 0; double fDepthHeight = 0; double rx = 0; double ry = 0; double rz = 0; double fTan = 0; double fx = 0; double fz = 0; fTan = (double)Math.Tan((float)Utils.Radiansf(m_fFovY/2)); // Get the cameras rotatiom about the LookAt position, as we // will use this to restore the rotation values after we move // the camera and it's focal length. GetRotationAboutLookAt(ref rx, ref ry, ref rz); // Copy the bounds to our local variable boundsMin.x = minX; boundsMin.y = minY; boundsMin.z = minZ; boundsMax.x = maxX; boundsMax.y = maxY; boundsMax.z = maxZ; double spanX, spanY, spanZ; spanX = Utils.Diff(boundsMax.x, boundsMin.x); spanY = Utils.Diff(boundsMax.y, boundsMin.y); spanZ = Utils.Diff(boundsMax.z, boundsMin.z); vecCenter.x = boundsMax.x - Math.Abs(spanX)/2; vecCenter.y = boundsMax.y - Math.Abs(spanY)/2; vecCenter.z = boundsMax.z - Math.Abs(spanZ)/2; boundsMax.x = spanX/2; boundsMax.y = spanY/2; boundsMax.z = spanZ/2; boundsMin.x = -spanX/2; boundsMin.y = -spanY/2; boundsMin.z = -spanZ/2; // Given the bounding box, fill in the missing vertices to complete our // cube vertices[0] = boundsMax; // Left vertices[1].x = boundsMax.x; vertices[1].y = boundsMax.y; vertices[1].z = boundsMin.x; vertices[2].x = boundsMax.x; vertices[2].y = boundsMin.y; vertices[2].z = boundsMin.x; vertices[3].x = boundsMax.x; vertices[3].y = boundsMin.y; vertices[3].z = boundsMax.x; vertices[4] = boundsMin; vertices[5].x = boundsMin.x; vertices[5].y = boundsMin.y; vertices[5].z = boundsMax.x; vertices[6].x = boundsMin.x; vertices[6].y = boundsMax.y; vertices[6].z = boundsMax.x; vertices[7].x = boundsMin.x; vertices[7].y = boundsMax.y; vertices[7].z = boundsMin.x; // Get the cameras rotation matrix GetRotationMatrix(ref matrix); for(int i=0; i<8; i++) { // Transform the vertice by the camera rotation matrix. Since we define the // default 'Up' camera position as Z-axis Up, the coordinates map as follows: // X maps to Width, // Y maps to Depth // Z mpas to Height zero_p.x = zero_p.y =zero_p.z =0.0; matrix.ApplyMatrixToVector(zero_p, vertices[i]); // Calculate the focal length needed to fit the near bounding plane fDepthWidth = (Math.Abs(vertices[i].x)/fTan/m_fAspect)-vertices[i].y; fDepthHeight = (Math.Abs(vertices[i].z) / fTan) - vertices[i].y; // Calculate the Near clipping bounds. This will be used to fit Isometric views and // for calculating the Near/Far clipping m_fFrustum. if(vertices[i].y<0) { if (Math.Abs(vertices[i].x) > Math.Abs(boundsNear.vec.x) || (Math.Abs(vertices[i].x) == boundsNear.vec.x && Math.Abs(vertices[i].y) > Math.Abs(boundsNear.vec.z))) { boundsNear.vec.x = Math.Abs(vertices[i].x); boundsNear.vec.z = Math.Abs(vertices[i].y); } if (Math.Abs(vertices[i].z) > Math.Abs(boundsNear.vec.y) || (Math.Abs(vertices[i].z) == boundsNear.vec.y)) { boundsNear.vec.y = Math.Abs(vertices[i].z); //boundsNear.vec[W] = fabs(vertices[i].y); } // Get the bounding depth closest to the viewer if(fDepthWidth < boundsNear.fDepth || boundsNear.fDepth == 0) boundsNear.fDepth = fDepthWidth; if(fDepthHeight < boundsNear.fDepth || boundsNear.fDepth == 0) boundsNear.fDepth = fDepthHeight; } else { if( Math.Abs(vertices[i].x) > Math.Abs(boundsFar.vec.x) || (Math.Abs(vertices[i].x) == boundsFar.vec.x && Math.Abs(vertices[i].y) < Math.Abs(boundsFar.vec.z)) ) { boundsFar.vec.x = vertices[i].x; boundsFar.vec.z = vertices[i].y; } if( Math.Abs(vertices[i].z) > Math.Abs(boundsFar.vec.y) || (Math.Abs(vertices[i].z) == Math.Abs(boundsFar.vec.y)) ) { boundsFar.vec.y = vertices[i].z; //boundsFar.vec[W] = vertices[i].y; } // Get the bounding depth furtherest from the viewer if(fDepthWidth > boundsFar.fDepth) boundsFar.fDepth = fDepthWidth; if(fDepthHeight > boundsFar.fDepth) boundsFar.fDepth = fDepthHeight; } // Calculate the Right, Left, Top and Bottom clipping bounds. This will be used to fit // Perspective views. if(vertices[i].x > 0) { if(fDepthWidth > boundsRight.fDepth) { boundsRight.fDepth = fDepthWidth; boundsRight.vec.x = vertices[i].x; //boundsRight.vec[W] = vertices[i].y; } } if(vertices[i].x <= 0) { if(fDepthWidth > boundsLeft.fDepth) { boundsLeft.fDepth = fDepthWidth; boundsLeft.vec.x = vertices[i].x; //boundsLeft.vec[W] = vertices[i].y; } } if(vertices[i].z > 0) { if(fDepthHeight > boundsTop.fDepth) { boundsTop.fDepth = fDepthHeight; boundsTop.vec.x = vertices[i].x; //boundsTop.vec[W] = vertices[i].y; } } if(vertices[i].z <= 0) { if(fDepthHeight > boundsBottom.fDepth) { boundsBottom.fDepth = fDepthHeight; boundsBottom.vec.x = vertices[i].x; //boundsBottom.vec[W] = vertices[i].y; } } } // Now that we have the view clipping bounds, we can calculate the focal depth // required to fit the volumn and the offset necessary to center the volumn. if (m_bPerspective) { msgMatrix invMatrix = new msgMatrix(); if (boundsRight.fDepth == boundsLeft.fDepth && boundsTop.fDepth == boundsBottom.fDepth) { // Front, Side or Top view // Since the bounds are symetric, just use the Right and Top focal depth. fx = boundsRight.fDepth; fz = boundsTop.fDepth; // No offset necessary vecOffset.x = vecOffset.y = vecOffset.z = 0.0; } else { // Calculate the average focal length needed to fit the bounding box fx = (boundsRight.fDepth + boundsLeft.fDepth) / 2; fz = (boundsTop.fDepth + boundsBottom.fDepth) / 2; // Calculate the offset necessary to center the bounding box. Note that we // use a scaling factor for centering the non-limiting bounds to achieve a // more visually appealing center. if (fx > fz) { double fScale = Math.Sqrt(boundsTop.fDepth / boundsBottom.fDepth); double fTop = fTan * fx - fTan * boundsTop.fDepth; double fBottom = fTan * fx - fTan * boundsBottom.fDepth; vecOffset.x = (fTan * m_fAspect * boundsRight.fDepth - fTan * m_fAspect * fx); vecOffset.z = (fBottom - fTop * fScale) / 2; } else { double fScale = Math.Sqrt(boundsLeft.fDepth / boundsRight.fDepth); double fRight = fTan * m_fAspect * fz - fTan * m_fAspect * boundsRight.fDepth; double fLeft = fTan * m_fAspect * fz - fTan * m_fAspect * boundsLeft.fDepth; vecOffset.z = (fTan * boundsTop.fDepth - fTan * fz); vecOffset.x = (fLeft - fRight * fScale) / 2; } } // Now that we have the offsets necessary to center the bounds, we must rotate // the vertices (camera coordinates) by the cameras inverse rotation matrix to // convert the offsets to world coordinates. GetInvRotationMatrix(ref invMatrix); zero_p.x = zero_p.y = zero_p.z = 0.0; invMatrix.ApplyMatrixToVector(zero_p, vecOffset); } else { // Isometric View // Calculate the focal length needed to fit the near bounding plane if (m_iScreenWidth <= m_iScreenHeight) { fx = boundsNear.vec.x / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)); fz = boundsNear.vec.y / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)) / ((double)m_iScreenHeight / (double)m_iScreenWidth); } else { fx = boundsNear.vec.x / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)) / m_fAspect; fz = boundsNear.vec.y / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)); } } // Set the focal length equal to the largest length required to fit either the // Width (Horizontal) or Height (Vertical) focalLength = (fx > fz ? fx : fz); // Set the camera's new LookAt position to focus on the center // of the bounding box. SetLookAtPos(vecCenter.x + vecOffset.x, vecCenter.y + vecOffset.y, vecCenter.z + vecOffset.z); // Set the camera focal Length if (focalLength > m_fNear) SetFocalLength(focalLength, true); // Adjust the Near clipping plane if necessary // if((boundsNear.fDepth/2) > 0.5f) // m_fNear = boundsNear.fDepth/2; // Adjust the Far clipping plane if necessary if (focalLength + boundsFar.fDepth > m_fFar) m_fFar = focalLength + boundsFar.fDepth; // Recalculate the camera view m_fFrustum; ResetView(0, 0); // Restore the cameras rotation about the LookAt position SetRotationAboutLookAt(rx, ry, rz); }
public void FitBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { BoundingPlane boundsRight = new BoundingPlane(); BoundingPlane boundsLeft = new BoundingPlane(); BoundingPlane boundsTop = new BoundingPlane(); BoundingPlane boundsBottom = new BoundingPlane(); BoundingPlane boundsNear = new BoundingPlane(); BoundingPlane boundsFar = new BoundingPlane(); msgVectorStruct boundsMax = new msgVectorStruct(); msgVectorStruct boundsMin = new msgVectorStruct(); msgVectorStruct vecCenter = new msgVectorStruct(); msgVectorStruct[] vertices = new msgVectorStruct[8]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = new msgVectorStruct(); } msgVectorStruct vecOffset = new msgVectorStruct(); msgMatrix matrix = new msgMatrix(); double focalLength = 0; double fDepthWidth = 0; double fDepthHeight = 0; double rx = 0; double ry = 0; double rz = 0; double fTan = 0; double fx = 0; double fz = 0; fTan = (double)Math.Tan((float)Utils.Radiansf(m_fFovY / 2)); // Get the cameras rotatiom about the LookAt position, as we // will use this to restore the rotation values after we move // the camera and it's focal length. GetRotationAboutLookAt(ref rx, ref ry, ref rz); // Copy the bounds to our local variable boundsMin.x = minX; boundsMin.y = minY; boundsMin.z = minZ; boundsMax.x = maxX; boundsMax.y = maxY; boundsMax.z = maxZ; double spanX, spanY, spanZ; spanX = Utils.Diff(boundsMax.x, boundsMin.x); spanY = Utils.Diff(boundsMax.y, boundsMin.y); spanZ = Utils.Diff(boundsMax.z, boundsMin.z); vecCenter.x = boundsMax.x - Math.Abs(spanX) / 2; vecCenter.y = boundsMax.y - Math.Abs(spanY) / 2; vecCenter.z = boundsMax.z - Math.Abs(spanZ) / 2; boundsMax.x = spanX / 2; boundsMax.y = spanY / 2; boundsMax.z = spanZ / 2; boundsMin.x = -spanX / 2; boundsMin.y = -spanY / 2; boundsMin.z = -spanZ / 2; // Given the bounding box, fill in the missing vertices to complete our // cube vertices[0] = boundsMax; // Left vertices[1].x = boundsMax.x; vertices[1].y = boundsMax.y; vertices[1].z = boundsMin.x; vertices[2].x = boundsMax.x; vertices[2].y = boundsMin.y; vertices[2].z = boundsMin.x; vertices[3].x = boundsMax.x; vertices[3].y = boundsMin.y; vertices[3].z = boundsMax.x; vertices[4] = boundsMin; vertices[5].x = boundsMin.x; vertices[5].y = boundsMin.y; vertices[5].z = boundsMax.x; vertices[6].x = boundsMin.x; vertices[6].y = boundsMax.y; vertices[6].z = boundsMax.x; vertices[7].x = boundsMin.x; vertices[7].y = boundsMax.y; vertices[7].z = boundsMin.x; // Get the cameras rotation matrix GetRotationMatrix(ref matrix); for (int i = 0; i < 8; i++) { // Transform the vertice by the camera rotation matrix. Since we define the // default 'Up' camera position as Z-axis Up, the coordinates map as follows: // X maps to Width, // Y maps to Depth // Z mpas to Height zero_p.x = zero_p.y = zero_p.z = 0.0; matrix.ApplyMatrixToVector(zero_p, vertices[i]); // Calculate the focal length needed to fit the near bounding plane fDepthWidth = (Math.Abs(vertices[i].x) / fTan / m_fAspect) - vertices[i].y; fDepthHeight = (Math.Abs(vertices[i].z) / fTan) - vertices[i].y; // Calculate the Near clipping bounds. This will be used to fit Isometric views and // for calculating the Near/Far clipping m_fFrustum. if (vertices[i].y < 0) { if (Math.Abs(vertices[i].x) > Math.Abs(boundsNear.vec.x) || (Math.Abs(vertices[i].x) == boundsNear.vec.x && Math.Abs(vertices[i].y) > Math.Abs(boundsNear.vec.z))) { boundsNear.vec.x = Math.Abs(vertices[i].x); boundsNear.vec.z = Math.Abs(vertices[i].y); } if (Math.Abs(vertices[i].z) > Math.Abs(boundsNear.vec.y) || (Math.Abs(vertices[i].z) == boundsNear.vec.y)) { boundsNear.vec.y = Math.Abs(vertices[i].z); //boundsNear.vec[W] = fabs(vertices[i].y); } // Get the bounding depth closest to the viewer if (fDepthWidth < boundsNear.fDepth || boundsNear.fDepth == 0) { boundsNear.fDepth = fDepthWidth; } if (fDepthHeight < boundsNear.fDepth || boundsNear.fDepth == 0) { boundsNear.fDepth = fDepthHeight; } } else { if (Math.Abs(vertices[i].x) > Math.Abs(boundsFar.vec.x) || (Math.Abs(vertices[i].x) == boundsFar.vec.x && Math.Abs(vertices[i].y) < Math.Abs(boundsFar.vec.z))) { boundsFar.vec.x = vertices[i].x; boundsFar.vec.z = vertices[i].y; } if (Math.Abs(vertices[i].z) > Math.Abs(boundsFar.vec.y) || (Math.Abs(vertices[i].z) == Math.Abs(boundsFar.vec.y))) { boundsFar.vec.y = vertices[i].z; //boundsFar.vec[W] = vertices[i].y; } // Get the bounding depth furtherest from the viewer if (fDepthWidth > boundsFar.fDepth) { boundsFar.fDepth = fDepthWidth; } if (fDepthHeight > boundsFar.fDepth) { boundsFar.fDepth = fDepthHeight; } } // Calculate the Right, Left, Top and Bottom clipping bounds. This will be used to fit // Perspective views. if (vertices[i].x > 0) { if (fDepthWidth > boundsRight.fDepth) { boundsRight.fDepth = fDepthWidth; boundsRight.vec.x = vertices[i].x; //boundsRight.vec[W] = vertices[i].y; } } if (vertices[i].x <= 0) { if (fDepthWidth > boundsLeft.fDepth) { boundsLeft.fDepth = fDepthWidth; boundsLeft.vec.x = vertices[i].x; //boundsLeft.vec[W] = vertices[i].y; } } if (vertices[i].z > 0) { if (fDepthHeight > boundsTop.fDepth) { boundsTop.fDepth = fDepthHeight; boundsTop.vec.x = vertices[i].x; //boundsTop.vec[W] = vertices[i].y; } } if (vertices[i].z <= 0) { if (fDepthHeight > boundsBottom.fDepth) { boundsBottom.fDepth = fDepthHeight; boundsBottom.vec.x = vertices[i].x; //boundsBottom.vec[W] = vertices[i].y; } } } // Now that we have the view clipping bounds, we can calculate the focal depth // required to fit the volumn and the offset necessary to center the volumn. if (m_bPerspective) { msgMatrix invMatrix = new msgMatrix(); if (boundsRight.fDepth == boundsLeft.fDepth && boundsTop.fDepth == boundsBottom.fDepth) { // Front, Side or Top view // Since the bounds are symetric, just use the Right and Top focal depth. fx = boundsRight.fDepth; fz = boundsTop.fDepth; // No offset necessary vecOffset.x = vecOffset.y = vecOffset.z = 0.0; } else { // Calculate the average focal length needed to fit the bounding box fx = (boundsRight.fDepth + boundsLeft.fDepth) / 2; fz = (boundsTop.fDepth + boundsBottom.fDepth) / 2; // Calculate the offset necessary to center the bounding box. Note that we // use a scaling factor for centering the non-limiting bounds to achieve a // more visually appealing center. if (fx > fz) { double fScale = Math.Sqrt(boundsTop.fDepth / boundsBottom.fDepth); double fTop = fTan * fx - fTan * boundsTop.fDepth; double fBottom = fTan * fx - fTan * boundsBottom.fDepth; vecOffset.x = (fTan * m_fAspect * boundsRight.fDepth - fTan * m_fAspect * fx); vecOffset.z = (fBottom - fTop * fScale) / 2; } else { double fScale = Math.Sqrt(boundsLeft.fDepth / boundsRight.fDepth); double fRight = fTan * m_fAspect * fz - fTan * m_fAspect * boundsRight.fDepth; double fLeft = fTan * m_fAspect * fz - fTan * m_fAspect * boundsLeft.fDepth; vecOffset.z = (fTan * boundsTop.fDepth - fTan * fz); vecOffset.x = (fLeft - fRight * fScale) / 2; } } // Now that we have the offsets necessary to center the bounds, we must rotate // the vertices (camera coordinates) by the cameras inverse rotation matrix to // convert the offsets to world coordinates. GetInvRotationMatrix(ref invMatrix); zero_p.x = zero_p.y = zero_p.z = 0.0; invMatrix.ApplyMatrixToVector(zero_p, vecOffset); } else { // Isometric View // Calculate the focal length needed to fit the near bounding plane if (m_iScreenWidth <= m_iScreenHeight) { fx = boundsNear.vec.x / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)); fz = boundsNear.vec.y / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)) / ((double)m_iScreenHeight / (double)m_iScreenWidth); } else { fx = boundsNear.vec.x / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)) / m_fAspect; fz = boundsNear.vec.y / Math.Tan((float)Utils.Radiansf(m_fFovY / 2)); } } // Set the focal length equal to the largest length required to fit either the // Width (Horizontal) or Height (Vertical) focalLength = (fx > fz ? fx : fz); // Set the camera's new LookAt position to focus on the center // of the bounding box. SetLookAtPos(vecCenter.x + vecOffset.x, vecCenter.y + vecOffset.y, vecCenter.z + vecOffset.z); // Set the camera focal Length if (focalLength > m_fNear) { SetFocalLength(focalLength, true); } // Adjust the Near clipping plane if necessary // if((boundsNear.fDepth/2) > 0.5f) // m_fNear = boundsNear.fDepth/2; // Adjust the Far clipping plane if necessary if (focalLength + boundsFar.fDepth > m_fFar) { m_fFar = focalLength + boundsFar.fDepth; } // Recalculate the camera view m_fFrustum; ResetView(0, 0); // Restore the cameras rotation about the LookAt position SetRotationAboutLookAt(rx, ry, rz); }