/indra/newview/llviewermediafocus.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 607 lines · 430 code · 81 blank · 96 comment · 76 complexity · 633aca4f7e103a5eb8606dd4c6a97611 MD5 · raw file

  1. /**
  2. * @file llviewermediafocus.cpp
  3. * @brief Governs focus on Media prims
  4. *
  5. * $LicenseInfo:firstyear=2003&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #include "llviewerprecompiledheaders.h"
  27. #include "llviewermediafocus.h"
  28. //LLViewerMediaFocus
  29. #include "llviewerobjectlist.h"
  30. #include "llpanelprimmediacontrols.h"
  31. #include "llpluginclassmedia.h"
  32. #include "llagent.h"
  33. #include "llagentcamera.h"
  34. #include "lltoolpie.h"
  35. #include "llviewercamera.h"
  36. #include "llviewermedia.h"
  37. #include "llhudview.h"
  38. #include "lluictrlfactory.h"
  39. #include "lldrawable.h"
  40. #include "llparcel.h"
  41. #include "llviewerparcelmgr.h"
  42. #include "llweb.h"
  43. #include "llmediaentry.h"
  44. #include "llkeyboard.h"
  45. #include "lltoolmgr.h"
  46. #include "llvovolume.h"
  47. #include "llhelp.h"
  48. //
  49. // LLViewerMediaFocus
  50. //
  51. LLViewerMediaFocus::LLViewerMediaFocus()
  52. : mFocusedObjectFace(0),
  53. mHoverObjectFace(0)
  54. {
  55. }
  56. LLViewerMediaFocus::~LLViewerMediaFocus()
  57. {
  58. // The destructor for LLSingletons happens at atexit() time, which is too late to do much.
  59. // Clean up in cleanupClass() instead.
  60. }
  61. void LLViewerMediaFocus::setFocusFace(LLPointer<LLViewerObject> objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal)
  62. {
  63. LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
  64. LLViewerMediaImpl *old_media_impl = getFocusedMediaImpl();
  65. if(old_media_impl)
  66. {
  67. old_media_impl->focus(false);
  68. }
  69. // Always clear the current selection. If we're setting focus on a face, we'll reselect the correct object below.
  70. LLSelectMgr::getInstance()->deselectAll();
  71. mSelection = NULL;
  72. if (media_impl.notNull() && objectp.notNull())
  73. {
  74. bool face_auto_zoom = false;
  75. mFocusedImplID = media_impl->getMediaTextureID();
  76. mFocusedObjectID = objectp->getID();
  77. mFocusedObjectFace = face;
  78. mFocusedObjectNormal = pick_normal;
  79. // Set the selection in the selection manager so we can draw the focus ring.
  80. mSelection = LLSelectMgr::getInstance()->selectObjectOnly(objectp, face);
  81. // Focusing on a media face clears its disable flag.
  82. media_impl->setDisabled(false);
  83. LLTextureEntry* tep = objectp->getTE(face);
  84. if(tep->hasMedia())
  85. {
  86. LLMediaEntry* mep = tep->getMediaData();
  87. face_auto_zoom = mep->getAutoZoom();
  88. if(!media_impl->hasMedia())
  89. {
  90. std::string url = mep->getCurrentURL().empty() ? mep->getHomeURL() : mep->getCurrentURL();
  91. media_impl->navigateTo(url, "", true);
  92. }
  93. }
  94. else
  95. {
  96. // This should never happen.
  97. llwarns << "Can't find media entry for focused face" << llendl;
  98. }
  99. media_impl->focus(true);
  100. gFocusMgr.setKeyboardFocus(this);
  101. LLViewerMediaImpl* impl = getFocusedMediaImpl();
  102. if (impl)
  103. {
  104. LLEditMenuHandler::gEditMenuHandler = impl;
  105. }
  106. // We must do this before processing the media HUD zoom, or it may zoom to the wrong face.
  107. update();
  108. if(mMediaControls.get())
  109. {
  110. if(face_auto_zoom && ! parcel->getMediaPreventCameraZoom())
  111. {
  112. // Zoom in on this face
  113. mMediaControls.get()->resetZoomLevel(false);
  114. mMediaControls.get()->nextZoomLevel();
  115. }
  116. else
  117. {
  118. // Reset the controls' zoom level without moving the camera.
  119. // This fixes the case where clicking focus between two non-autozoom faces doesn't change the zoom-out button back to a zoom-in button.
  120. mMediaControls.get()->resetZoomLevel(false);
  121. }
  122. }
  123. }
  124. else
  125. {
  126. if(hasFocus())
  127. {
  128. gFocusMgr.setKeyboardFocus(NULL);
  129. }
  130. LLViewerMediaImpl* impl = getFocusedMediaImpl();
  131. if (LLEditMenuHandler::gEditMenuHandler == impl)
  132. {
  133. LLEditMenuHandler::gEditMenuHandler = NULL;
  134. }
  135. mFocusedImplID = LLUUID::null;
  136. if (objectp.notNull())
  137. {
  138. // Still record the focused object...it may mean we need to load media data.
  139. // This will aid us in determining this object is "important enough"
  140. mFocusedObjectID = objectp->getID();
  141. mFocusedObjectFace = face;
  142. }
  143. else {
  144. mFocusedObjectID = LLUUID::null;
  145. mFocusedObjectFace = 0;
  146. }
  147. }
  148. }
  149. void LLViewerMediaFocus::clearFocus()
  150. {
  151. setFocusFace(NULL, 0, NULL);
  152. }
  153. void LLViewerMediaFocus::setHoverFace(LLPointer<LLViewerObject> objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal)
  154. {
  155. if (media_impl.notNull())
  156. {
  157. mHoverImplID = media_impl->getMediaTextureID();
  158. mHoverObjectID = objectp->getID();
  159. mHoverObjectFace = face;
  160. mHoverObjectNormal = pick_normal;
  161. }
  162. else
  163. {
  164. mHoverObjectID = LLUUID::null;
  165. mHoverObjectFace = 0;
  166. mHoverImplID = LLUUID::null;
  167. }
  168. }
  169. void LLViewerMediaFocus::clearHover()
  170. {
  171. setHoverFace(NULL, 0, NULL);
  172. }
  173. bool LLViewerMediaFocus::getFocus()
  174. {
  175. if (gFocusMgr.getKeyboardFocus() == this)
  176. {
  177. return true;
  178. }
  179. return false;
  180. }
  181. // This function selects an ideal viewing distance based on the focused object, pick normal, and padding value
  182. void LLViewerMediaFocus::setCameraZoom(LLViewerObject* object, LLVector3 normal, F32 padding_factor, bool zoom_in_only)
  183. {
  184. if (object)
  185. {
  186. gAgentCamera.setFocusOnAvatar(FALSE, ANIMATE);
  187. LLBBox bbox = object->getBoundingBoxAgent();
  188. LLVector3d center = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent());
  189. F32 height;
  190. F32 width;
  191. F32 depth;
  192. F32 angle_of_view;
  193. F32 distance;
  194. // We need the aspect ratio, and the 3 components of the bbox as height, width, and depth.
  195. F32 aspect_ratio = getBBoxAspectRatio(bbox, normal, &height, &width, &depth);
  196. F32 camera_aspect = LLViewerCamera::getInstance()->getAspect();
  197. lldebugs << "normal = " << normal << ", aspect_ratio = " << aspect_ratio << ", camera_aspect = " << camera_aspect << llendl;
  198. // We will normally use the side of the volume aligned with the short side of the screen (i.e. the height for
  199. // a screen in a landscape aspect ratio), however there is an edge case where the aspect ratio of the object is
  200. // more extreme than the screen. In this case we invert the logic, using the longer component of both the object
  201. // and the screen.
  202. bool invert = (camera_aspect > 1.0f && aspect_ratio > camera_aspect) ||
  203. (camera_aspect < 1.0f && aspect_ratio < camera_aspect);
  204. // To calculate the optimum viewing distance we will need the angle of the shorter side of the view rectangle.
  205. // In portrait mode this is the width, and in landscape it is the height.
  206. // We then calculate the distance based on the corresponding side of the object bbox (width for portrait, height for landscape)
  207. // We will add half the depth of the bounding box, as the distance projection uses the center point of the bbox.
  208. if(camera_aspect < 1.0f || invert)
  209. {
  210. angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect());
  211. distance = width * 0.5 * padding_factor / tan(angle_of_view * 0.5f );
  212. lldebugs << "using width (" << width << "), angle_of_view = " << angle_of_view << ", distance = " << distance << llendl;
  213. }
  214. else
  215. {
  216. angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView());
  217. distance = height * 0.5 * padding_factor / tan(angle_of_view * 0.5f );
  218. lldebugs << "using height (" << height << "), angle_of_view = " << angle_of_view << ", distance = " << distance << llendl;
  219. }
  220. distance += depth * 0.5;
  221. // Finally animate the camera to this new position and focal point
  222. LLVector3d camera_pos, target_pos;
  223. // The target lookat position is the center of the selection (in global coords)
  224. target_pos = center;
  225. // Target look-from (camera) position is "distance" away from the target along the normal
  226. LLVector3d pickNormal = LLVector3d(normal);
  227. pickNormal.normalize();
  228. camera_pos = target_pos + pickNormal * distance;
  229. if (pickNormal == LLVector3d::z_axis || pickNormal == LLVector3d::z_axis_neg)
  230. {
  231. // If the normal points directly up, the camera will "flip" around.
  232. // We try to avoid this by adjusting the target camera position a
  233. // smidge towards current camera position
  234. // *NOTE: this solution is not perfect. All it attempts to solve is the
  235. // "looking down" problem where the camera flips around when it animates
  236. // to that position. You still are not guaranteed to be looking at the
  237. // media in the correct orientation. What this solution does is it will
  238. // put the camera into position keeping as best it can the current
  239. // orientation with respect to the face. In other words, if before zoom
  240. // the media appears "upside down" from the camera, after zooming it will
  241. // still be upside down, but at least it will not flip.
  242. LLVector3d cur_camera_pos = LLVector3d(gAgentCamera.getCameraPositionGlobal());
  243. LLVector3d delta = (cur_camera_pos - camera_pos);
  244. F64 len = delta.length();
  245. delta.normalize();
  246. // Move 1% of the distance towards original camera location
  247. camera_pos += 0.01 * len * delta;
  248. }
  249. // If we are not allowing zooming out and the old camera position is closer to
  250. // the center then the new intended camera position, don't move camera and return
  251. if (zoom_in_only &&
  252. (dist_vec_squared(gAgentCamera.getCameraPositionGlobal(), target_pos) < dist_vec_squared(camera_pos, target_pos)))
  253. {
  254. return;
  255. }
  256. gAgentCamera.setCameraPosAndFocusGlobal(camera_pos, target_pos, object->getID() );
  257. }
  258. else
  259. {
  260. // If we have no object, focus back on the avatar.
  261. gAgentCamera.setFocusOnAvatar(TRUE, ANIMATE);
  262. }
  263. }
  264. void LLViewerMediaFocus::onFocusReceived()
  265. {
  266. LLViewerMediaImpl* media_impl = getFocusedMediaImpl();
  267. if(media_impl)
  268. media_impl->focus(true);
  269. LLFocusableElement::onFocusReceived();
  270. }
  271. void LLViewerMediaFocus::onFocusLost()
  272. {
  273. LLViewerMediaImpl* media_impl = getFocusedMediaImpl();
  274. if(media_impl)
  275. media_impl->focus(false);
  276. gViewerWindow->focusClient();
  277. LLFocusableElement::onFocusLost();
  278. }
  279. BOOL LLViewerMediaFocus::handleKey(KEY key, MASK mask, BOOL called_from_parent)
  280. {
  281. LLViewerMediaImpl* media_impl = getFocusedMediaImpl();
  282. if(media_impl)
  283. {
  284. media_impl->handleKeyHere(key, mask);
  285. if (KEY_ESCAPE == key)
  286. {
  287. // Reset camera zoom in this case.
  288. if(mFocusedImplID.notNull())
  289. {
  290. if(mMediaControls.get())
  291. {
  292. mMediaControls.get()->resetZoomLevel(true);
  293. }
  294. }
  295. clearFocus();
  296. }
  297. if ( KEY_F1 == key && LLUI::sHelpImpl && mMediaControls.get())
  298. {
  299. std::string help_topic;
  300. if (mMediaControls.get()->findHelpTopic(help_topic))
  301. {
  302. LLUI::sHelpImpl->showTopic(help_topic);
  303. }
  304. }
  305. }
  306. return true;
  307. }
  308. BOOL LLViewerMediaFocus::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent)
  309. {
  310. LLViewerMediaImpl* media_impl = getFocusedMediaImpl();
  311. if(media_impl)
  312. media_impl->handleUnicodeCharHere(uni_char);
  313. return true;
  314. }
  315. BOOL LLViewerMediaFocus::handleScrollWheel(S32 x, S32 y, S32 clicks)
  316. {
  317. BOOL retval = FALSE;
  318. LLViewerMediaImpl* media_impl = getFocusedMediaImpl();
  319. if(media_impl && media_impl->hasMedia())
  320. {
  321. // the scrollEvent() API's x and y are not the same as handleScrollWheel's x and y.
  322. // The latter is the position of the mouse at the time of the event
  323. // The former is the 'scroll amount' in x and y, respectively.
  324. // All we have for 'scroll amount' here is 'clicks'.
  325. // We're also not passed the keyboard modifier mask, but we can get that from gKeyboard.
  326. media_impl->getMediaPlugin()->scrollEvent(0, clicks, gKeyboard->currentMask(TRUE));
  327. retval = TRUE;
  328. }
  329. return retval;
  330. }
  331. void LLViewerMediaFocus::update()
  332. {
  333. if(mFocusedImplID.notNull())
  334. {
  335. // We have a focused impl/face.
  336. if(!getFocus())
  337. {
  338. // We've lost keyboard focus -- check to see whether the media controls have it
  339. if(mMediaControls.get() && mMediaControls.get()->hasFocus())
  340. {
  341. // the media controls have focus -- don't clear.
  342. }
  343. else
  344. {
  345. // Someone else has focus -- back off.
  346. clearFocus();
  347. }
  348. }
  349. else if(LLToolMgr::getInstance()->inBuildMode())
  350. {
  351. // Build tools are selected -- clear focus.
  352. clearFocus();
  353. }
  354. }
  355. LLViewerMediaImpl *media_impl = getFocusedMediaImpl();
  356. LLViewerObject *viewer_object = getFocusedObject();
  357. S32 face = mFocusedObjectFace;
  358. LLVector3 normal = mFocusedObjectNormal;
  359. bool focus = true;
  360. if(!media_impl || !viewer_object)
  361. {
  362. focus = false;
  363. media_impl = getHoverMediaImpl();
  364. viewer_object = getHoverObject();
  365. face = mHoverObjectFace;
  366. normal = mHoverObjectNormal;
  367. }
  368. if(media_impl && viewer_object)
  369. {
  370. // We have an object and impl to point at.
  371. // Make sure the media HUD object exists.
  372. if(! mMediaControls.get())
  373. {
  374. LLPanelPrimMediaControls* media_controls = new LLPanelPrimMediaControls();
  375. mMediaControls = media_controls->getHandle();
  376. gHUDView->addChild(media_controls);
  377. }
  378. mMediaControls.get()->setMediaFace(viewer_object, face, media_impl, normal);
  379. }
  380. else
  381. {
  382. // The media HUD is no longer needed.
  383. if(mMediaControls.get())
  384. {
  385. mMediaControls.get()->setMediaFace(NULL, 0, NULL);
  386. }
  387. }
  388. }
  389. // This function calculates the aspect ratio and the world aligned components of a selection bounding box.
  390. F32 LLViewerMediaFocus::getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth)
  391. {
  392. // Convert the selection normal and an up vector to local coordinate space of the bbox
  393. LLVector3 local_normal = bbox.agentToLocalBasis(normal);
  394. LLVector3 z_vec = bbox.agentToLocalBasis(LLVector3(0.0f, 0.0f, 1.0f));
  395. LLVector3 comp1(0.f,0.f,0.f);
  396. LLVector3 comp2(0.f,0.f,0.f);
  397. LLVector3 bbox_max = bbox.getExtentLocal();
  398. F32 dot1 = 0.f;
  399. F32 dot2 = 0.f;
  400. lldebugs << "bounding box local size = " << bbox_max << ", local_normal = " << local_normal << llendl;
  401. // The largest component of the localized normal vector is the depth component
  402. // meaning that the other two are the legs of the rectangle.
  403. local_normal.abs();
  404. // Using temporary variables for these makes the logic a bit more readable.
  405. bool XgtY = (local_normal.mV[VX] > local_normal.mV[VY]);
  406. bool XgtZ = (local_normal.mV[VX] > local_normal.mV[VZ]);
  407. bool YgtZ = (local_normal.mV[VY] > local_normal.mV[VZ]);
  408. if(XgtY && XgtZ)
  409. {
  410. lldebugs << "x component of normal is longest, using y and z" << llendl;
  411. comp1.mV[VY] = bbox_max.mV[VY];
  412. comp2.mV[VZ] = bbox_max.mV[VZ];
  413. *depth = bbox_max.mV[VX];
  414. }
  415. else if(!XgtY && YgtZ)
  416. {
  417. lldebugs << "y component of normal is longest, using x and z" << llendl;
  418. comp1.mV[VX] = bbox_max.mV[VX];
  419. comp2.mV[VZ] = bbox_max.mV[VZ];
  420. *depth = bbox_max.mV[VY];
  421. }
  422. else
  423. {
  424. lldebugs << "z component of normal is longest, using x and y" << llendl;
  425. comp1.mV[VX] = bbox_max.mV[VX];
  426. comp2.mV[VY] = bbox_max.mV[VY];
  427. *depth = bbox_max.mV[VZ];
  428. }
  429. // The height is the vector closest to vertical in the bbox coordinate space (highest dot product value)
  430. dot1 = comp1 * z_vec;
  431. dot2 = comp2 * z_vec;
  432. if(fabs(dot1) > fabs(dot2))
  433. {
  434. *height = comp1.length();
  435. *width = comp2.length();
  436. lldebugs << "comp1 = " << comp1 << ", height = " << *height << llendl;
  437. lldebugs << "comp2 = " << comp2 << ", width = " << *width << llendl;
  438. }
  439. else
  440. {
  441. *height = comp2.length();
  442. *width = comp1.length();
  443. lldebugs << "comp2 = " << comp2 << ", height = " << *height << llendl;
  444. lldebugs << "comp1 = " << comp1 << ", width = " << *width << llendl;
  445. }
  446. lldebugs << "returning " << (*width / *height) << llendl;
  447. // Return the aspect ratio.
  448. return *width / *height;
  449. }
  450. bool LLViewerMediaFocus::isFocusedOnFace(LLPointer<LLViewerObject> objectp, S32 face)
  451. {
  452. return objectp->getID() == mFocusedObjectID && face == mFocusedObjectFace;
  453. }
  454. bool LLViewerMediaFocus::isHoveringOverFace(LLPointer<LLViewerObject> objectp, S32 face)
  455. {
  456. return objectp->getID() == mHoverObjectID && face == mHoverObjectFace;
  457. }
  458. LLViewerMediaImpl* LLViewerMediaFocus::getFocusedMediaImpl()
  459. {
  460. return LLViewerMedia::getMediaImplFromTextureID(mFocusedImplID);
  461. }
  462. LLViewerObject* LLViewerMediaFocus::getFocusedObject()
  463. {
  464. return gObjectList.findObject(mFocusedObjectID);
  465. }
  466. LLViewerMediaImpl* LLViewerMediaFocus::getHoverMediaImpl()
  467. {
  468. return LLViewerMedia::getMediaImplFromTextureID(mHoverImplID);
  469. }
  470. LLViewerObject* LLViewerMediaFocus::getHoverObject()
  471. {
  472. return gObjectList.findObject(mHoverObjectID);
  473. }
  474. void LLViewerMediaFocus::focusZoomOnMedia(LLUUID media_id)
  475. {
  476. LLViewerMediaImpl* impl = LLViewerMedia::getMediaImplFromTextureID(media_id);
  477. if(impl)
  478. {
  479. // Get the first object from the media impl's object list. This is completely arbitrary, but should suffice.
  480. LLVOVolume *obj = impl->getSomeObject();
  481. if(obj)
  482. {
  483. // This media is attached to at least one object. Figure out which face it's on.
  484. S32 face = obj->getFaceIndexWithMediaImpl(impl, -1);
  485. // We don't have a proper pick normal here, and finding a face's real normal is... complicated.
  486. LLVector3 normal = obj->getApproximateFaceNormal(face);
  487. if(normal.isNull())
  488. {
  489. // If that didn't work, use the inverse of the camera "look at" axis, which should keep the camera pointed in the same direction.
  490. // llinfos << "approximate face normal invalid, using camera direction." << llendl;
  491. normal = LLViewerCamera::getInstance()->getAtAxis();
  492. normal *= (F32)-1.0f;
  493. }
  494. // Attempt to focus/zoom on that face.
  495. setFocusFace(obj, face, impl, normal);
  496. if(mMediaControls.get())
  497. {
  498. mMediaControls.get()->resetZoomLevel();
  499. mMediaControls.get()->nextZoomLevel();
  500. }
  501. }
  502. }
  503. }
  504. void LLViewerMediaFocus::unZoom()
  505. {
  506. if(mMediaControls.get())
  507. {
  508. mMediaControls.get()->resetZoomLevel();
  509. }
  510. }
  511. bool LLViewerMediaFocus::isZoomed() const
  512. {
  513. return (mMediaControls.get() && mMediaControls.get()->getZoomLevel() != LLPanelPrimMediaControls::ZOOM_NONE);
  514. }
  515. LLUUID LLViewerMediaFocus::getControlsMediaID()
  516. {
  517. if(getFocusedMediaImpl())
  518. {
  519. return mFocusedImplID;
  520. }
  521. else if(getHoverMediaImpl())
  522. {
  523. return mHoverImplID;
  524. }
  525. return LLUUID::null;
  526. }