qltoolalign.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. /**
  2. * @file qltoolalign.cpp
  3. * @brief A tool to align objects
  4. *
  5. * $LicenseInfo:firstyear=2010&license=viewergpl$
  6. *
  7. * Copyright (c) 2010, Qarl Linden
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "llviewerprecompiledheaders.h"
  33. #include "qltoolalign.h"
  34. #include "llbbox.h"
  35. #include "llrenderutils.h" // For gBox, gCone
  36. #include "llagent.h"
  37. #include "llfloatertools.h"
  38. #include "llselectmgr.h"
  39. #include "llviewercamera.h"
  40. #include "llviewercontrol.h"
  41. #include "llviewerobject.h"
  42. #include "llviewerwindow.h"
  43. constexpr F32 MANIPULATOR_SIZE = 5.0;
  44. constexpr F32 MANIPULATOR_SELECT_SIZE = 20.0;
  45. QLToolAlign gToolAlign;
  46. QLToolAlign::QLToolAlign()
  47. : LLTool("Align")
  48. {
  49. }
  50. bool QLToolAlign::handleMouseDown(S32 x, S32 y, MASK mask)
  51. {
  52. if (mHighlightedAxis != -1)
  53. {
  54. align();
  55. }
  56. else if (gViewerWindowp)
  57. {
  58. gViewerWindowp->pickAsync(x, y, mask, pickCallback);
  59. }
  60. return true;
  61. }
  62. bool QLToolAlign::handleMouseUp(S32 x, S32 y, MASK mask)
  63. {
  64. // first, perform normal processing in case this was a quick-click
  65. handleHover(x, y, mask);
  66. gSelectMgr.updateSelectionCenter();
  67. bool handled = false;
  68. if (hasMouseCapture())
  69. {
  70. handled = true;
  71. setMouseCapture(false);
  72. }
  73. return handled;
  74. }
  75. void QLToolAlign::pickCallback(const LLPickInfo& pick_info)
  76. {
  77. LLViewerObject* object = pick_info.getObject();
  78. if (object)
  79. {
  80. if (object->isAvatar())
  81. {
  82. return;
  83. }
  84. if (pick_info.mKeyMask & MASK_SHIFT)
  85. {
  86. // If object not selected, select it
  87. if (!object->isSelected())
  88. {
  89. gSelectMgr.selectObjectAndFamily(object);
  90. }
  91. else
  92. {
  93. gSelectMgr.deselectObjectAndFamily(object);
  94. }
  95. }
  96. else
  97. {
  98. gSelectMgr.deselectAll();
  99. gSelectMgr.selectObjectAndFamily(object);
  100. }
  101. }
  102. else
  103. {
  104. if (pick_info.mKeyMask != MASK_SHIFT)
  105. {
  106. gSelectMgr.deselectAll();
  107. }
  108. }
  109. gSelectMgr.promoteSelectionToRoot();
  110. }
  111. void QLToolAlign::handleSelect()
  112. {
  113. LL_DEBUGS("ToolAlign") << "Tool Align in select." << LL_ENDL;
  114. // No parts, please
  115. gSelectMgr.promoteSelectionToRoot();
  116. gSelectMgr.updateSelectionCenter();
  117. if (gFloaterToolsp)
  118. {
  119. gFloaterToolsp->setStatusText("align");
  120. }
  121. }
  122. bool QLToolAlign::findSelectedManipulator(S32 x, S32 y)
  123. {
  124. if (!gViewerWindowp) return false;
  125. mHighlightedAxis = -1;
  126. mHighlightedDirection = 0;
  127. LLMatrix4 transform;
  128. if (gSelectMgr.getSelection()->getSelectType() == SELECT_TYPE_HUD)
  129. {
  130. LLVector4 translation(mBBox.getCenterAgent());
  131. transform.initRotTrans(mBBox.getRotation(), translation);
  132. LLMatrix4 cfr(OGL_TO_CFR_ROTATION);
  133. transform *= cfr;
  134. LLMatrix4 window_scale;
  135. F32 zoom_level = 2.f * gAgent.mHUDCurZoom;
  136. window_scale.initAll(LLVector3(zoom_level / gViewerCamera.getAspect(),
  137. zoom_level, 0.f),
  138. LLQuaternion::DEFAULT,
  139. LLVector3::zero);
  140. transform *= window_scale;
  141. }
  142. else
  143. {
  144. transform.initAll(LLVector3(1.f, 1.f, 1.f), mBBox.getRotation(), mBBox.getCenterAgent());
  145. LLMatrix4 projection_matrix = gViewerCamera.getProjection();
  146. LLMatrix4 model_matrix = gViewerCamera.getModelview();
  147. transform *= model_matrix;
  148. transform *= projection_matrix;
  149. }
  150. F32 half_width = (F32)gViewerWindowp->getWindowWidth() * 0.5f;
  151. F32 half_height = (F32)gViewerWindowp->getWindowHeight() * 0.5f;
  152. LLVector2 manip2d;
  153. LLVector2 mousePos((F32)x - half_width, (F32)y - half_height);
  154. LLVector2 delta;
  155. LLVector3 bbox_scale = mBBox.getMaxLocal() - mBBox.getMinLocal();
  156. for (S32 axis = VX; axis <= (S32)VZ; ++axis)
  157. {
  158. for (F32 direction = -1.0; direction <= 1.0; direction += 2.0)
  159. {
  160. LLVector3 axis_vector = LLVector3(0, 0, 0);
  161. axis_vector.mV[axis] = direction * bbox_scale.mV[axis] * 0.5f;
  162. LLVector4 manipulator_center = LLVector4(axis_vector);
  163. LLVector4 screen_center = manipulator_center * transform;
  164. screen_center /= screen_center.mV[VW];
  165. manip2d.set(screen_center.mV[VX] * half_width,
  166. screen_center.mV[VY] * half_height);
  167. delta = manip2d - mousePos;
  168. if (delta.lengthSquared() < MANIPULATOR_SELECT_SIZE * MANIPULATOR_SELECT_SIZE)
  169. {
  170. mHighlightedAxis = axis;
  171. mHighlightedDirection = direction;
  172. return true;
  173. }
  174. }
  175. }
  176. return false;
  177. }
  178. bool QLToolAlign::handleHover(S32 x, S32 y, MASK mask)
  179. {
  180. if (mask & MASK_SHIFT)
  181. {
  182. mForce = false;
  183. }
  184. else
  185. {
  186. mForce = true;
  187. }
  188. if (gViewerWindowp)
  189. {
  190. gViewerWindowp->setCursor(UI_CURSOR_ARROW);
  191. }
  192. return findSelectedManipulator(x, y);
  193. }
  194. void setup_transforms_bbox(LLBBox bbox)
  195. {
  196. // translate to center
  197. LLVector3 center = bbox.getCenterAgent();
  198. gGL.translatef(center.mV[VX], center.mV[VY], center.mV[VZ]);
  199. // rotate
  200. LLQuaternion rotation = bbox.getRotation();
  201. F32 angle_radians, x, y, z;
  202. rotation.getAngleAxis(&angle_radians, &x, &y, &z);
  203. // gGL has no rotate method (despite having translate and scale).
  204. // So we hack.
  205. gGL.flush();
  206. gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z);
  207. // scale
  208. LLVector3 scale = bbox.getMaxLocal() - bbox.getMinLocal();
  209. gGL.scalef(scale.mV[VX], scale.mV[VY], scale.mV[VZ]);
  210. }
  211. void render_bbox(LLBBox bbox)
  212. {
  213. gGL.matrixMode(LLRender::MM_MODELVIEW);
  214. gGL.pushMatrix();
  215. setup_transforms_bbox(bbox);
  216. gGL.flush();
  217. gBox.render();
  218. gGL.popMatrix();
  219. }
  220. void render_cone_bbox(LLBBox bbox)
  221. {
  222. gGL.matrixMode(LLRender::MM_MODELVIEW);
  223. gGL.pushMatrix();
  224. setup_transforms_bbox(bbox);
  225. gGL.flush();
  226. gCone.render();
  227. gGL.popMatrix();
  228. }
  229. // the selection bbox isn't axis aligned, so we must construct one
  230. // should this be cached in the selection manager? yes.
  231. LLBBox get_selection_axis_aligned_bbox()
  232. {
  233. LLBBox selection_bbox = gSelectMgr.getBBoxOfSelection();
  234. LLVector3 position = selection_bbox.getPositionAgent();
  235. LLBBox axis_aligned_bbox(position, LLQuaternion(), LLVector3::zero,
  236. LLVector3::zero);
  237. axis_aligned_bbox.addPointLocal(LLVector3::zero);
  238. // cycle over the nodes in selection
  239. for (LLObjectSelection::iterator it = gSelectMgr.getSelection()->begin(),
  240. end = gSelectMgr.getSelection()->end();
  241. it != end; ++it)
  242. {
  243. LLSelectNode* select_node = *it;
  244. if (select_node)
  245. {
  246. LLViewerObject* object = select_node->getObject();
  247. if (object)
  248. {
  249. axis_aligned_bbox.addBBoxAgent(object->getBoundingBoxAgent());
  250. }
  251. }
  252. }
  253. return axis_aligned_bbox;
  254. }
  255. void QLToolAlign::computeManipulatorSize()
  256. {
  257. if (gSelectMgr.getSelection()->getSelectType() == SELECT_TYPE_HUD)
  258. {
  259. mManipulatorSize = MANIPULATOR_SIZE / (gViewerCamera.getViewHeightInPixels() *
  260. gAgent.mHUDCurZoom);
  261. }
  262. else
  263. {
  264. F32 distance = dist_vec(gAgent.getCameraPositionAgent(), mBBox.getCenterAgent());
  265. if (distance > 0.001f)
  266. {
  267. // range != zero
  268. F32 fraction_of_fov = MANIPULATOR_SIZE / gViewerCamera.getViewHeightInPixels();
  269. F32 apparent_angle = fraction_of_fov * gViewerCamera.getView(); // radians
  270. mManipulatorSize = MANIPULATOR_SIZE * distance * tanf(apparent_angle);
  271. }
  272. else
  273. {
  274. // range == zero
  275. mManipulatorSize = MANIPULATOR_SIZE;
  276. }
  277. }
  278. }
  279. LLColor4 manipulator_color[3] = { LLColor4(0.7f, 0.0f, 0.0f, 0.5f),
  280. LLColor4(0.0f, 0.7f, 0.0f, 0.5f),
  281. LLColor4(0.0f, 0.0f, 0.7f, 0.5f) };
  282. void QLToolAlign::renderManipulators()
  283. {
  284. computeManipulatorSize();
  285. LLVector3 bbox_center = mBBox.getCenterAgent();
  286. LLVector3 bbox_scale = mBBox.getMaxLocal() - mBBox.getMinLocal();
  287. const S32 arrows = mForce ? 2 : 1;
  288. for (S32 axis = VX; axis <= (S32)VZ; ++axis)
  289. {
  290. for (F32 direction = -1.f; direction <= 1.f; direction += 2.f)
  291. {
  292. F32 size = mManipulatorSize;
  293. LLColor4 color = manipulator_color[axis];
  294. if (axis == mHighlightedAxis && direction == mHighlightedDirection)
  295. {
  296. size *= 2.f;
  297. color *= 1.5f;
  298. }
  299. const F32 size_third = size / 3.f;
  300. const LLVector3 vec1 = LLVector3(-1.f, -1.f, -0.75f) * size * 0.5f;
  301. const LLVector3 vec2 = LLVector3(1.f, 1.f, 0.75f) * size * 0.5f;
  302. LLVector3 axis_vector, manipulator_center;
  303. LLQuaternion manipulator_rotation;
  304. for (S32 i = 0; i < arrows; ++i)
  305. {
  306. axis_vector.clear();
  307. axis_vector.mV[axis] = direction *
  308. (bbox_scale.mV[axis] * 0.5f +
  309. i * size_third);
  310. manipulator_center = bbox_center + axis_vector;
  311. manipulator_rotation.shortestArc(LLVector3::z_axis,
  312. -1.f * axis_vector);
  313. LLBBox manipulator_bbox(manipulator_center,
  314. manipulator_rotation,
  315. LLVector3::zero, LLVector3::zero);
  316. manipulator_bbox.addPointLocal(vec1);
  317. manipulator_bbox.addPointLocal(vec2);
  318. gGL.color4fv(color.mV);
  319. // Sadly, gCone does not use gGL like gBox does so we also set
  320. // the raw GL color. Hopefully this would not screw-up later
  321. // rendering.
  322. glColor4fv(color.mV);
  323. render_cone_bbox(manipulator_bbox);
  324. }
  325. }
  326. }
  327. }
  328. void QLToolAlign::render()
  329. {
  330. mBBox = get_selection_axis_aligned_bbox();
  331. // Draw bounding box
  332. LLGLSUIDefault gls_ui;
  333. LLGLEnable gl_blend(GL_BLEND);
  334. LLGLDepthTest gls_depth(GL_FALSE);
  335. gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
  336. // Render box
  337. static const LLColor4 default_normal_color(0.7f, 0.7f, 0.7f, 0.1f);
  338. gGL.color4fv(default_normal_color.mV);
  339. LLObjectSelectionHandle selection = gSelectMgr.getEditSelection();
  340. bool can_move = (selection->getObjectCount() != 0);
  341. if (can_move)
  342. {
  343. struct f final : public LLSelectedObjectFunctor
  344. {
  345. bool apply(LLViewerObject* objectp) override
  346. {
  347. static LLCachedControl<bool> linked_parts(gSavedSettings,
  348. "EditLinkedParts");
  349. return objectp->permMove() &&
  350. (objectp->permModify() || !linked_parts);
  351. }
  352. } func;
  353. can_move = selection->applyToObjects(&func);
  354. }
  355. if (can_move)
  356. {
  357. render_bbox(mBBox);
  358. renderManipulators();
  359. }
  360. }
  361. // Only works for our specialized (AABB, position centered) bboxes
  362. bool bbox_overlap(LLBBox bbox1, LLBBox bbox2)
  363. {
  364. constexpr F32 FUDGE = 0.001f; // Because of stupid SL precision/rounding
  365. LLVector3 delta = bbox1.getCenterAgent() - bbox2.getCenterAgent();
  366. LLVector3 half_extent = (bbox1.getExtentLocal() +
  367. bbox2.getExtentLocal()) * 0.5f;
  368. return (fabs(delta.mV[VX]) < half_extent.mV[VX] - FUDGE &&
  369. fabs(delta.mV[VY]) < half_extent.mV[VY] - FUDGE &&
  370. fabs(delta.mV[VZ]) < half_extent.mV[VZ] - FUDGE);
  371. }
  372. // Used to sort bboxes before packing
  373. typedef std::map<LLPointer<LLViewerObject>, LLBBox> bbox_map_t;
  374. class BBoxCompare
  375. {
  376. public:
  377. BBoxCompare(S32 axis, F32 direction, bbox_map_t& bboxes)
  378. : mAxis(axis),
  379. mDirection(direction),
  380. mBBoxes(bboxes)
  381. {
  382. }
  383. bool operator() (LLViewerObject* object1, LLViewerObject* object2)
  384. {
  385. LLVector3 corner1 = mBBoxes[object1].getCenterAgent() -
  386. mDirection *
  387. mBBoxes[object1].getExtentLocal() * 0.5f;
  388. LLVector3 corner2 = mBBoxes[object2].getCenterAgent() -
  389. mDirection *
  390. mBBoxes[object2].getExtentLocal() * 0.5f;
  391. return mDirection * corner1.mV[mAxis] < mDirection * corner2.mV[mAxis];
  392. }
  393. public:
  394. bbox_map_t& mBBoxes;
  395. S32 mAxis;
  396. F32 mDirection;
  397. };
  398. void QLToolAlign::align()
  399. {
  400. // No linkset parts, please
  401. gSelectMgr.promoteSelectionToRoot();
  402. std::vector<LLPointer<LLViewerObject> > objects;
  403. bbox_map_t original_bboxes;
  404. // Cycle over the nodes in selection and collect them into an array
  405. for (LLObjectSelection::root_iterator
  406. it = gSelectMgr.getSelection()->root_begin(),
  407. end = gSelectMgr.getSelection()->root_end();
  408. it != end; ++it)
  409. {
  410. LLSelectNode* select_node = *it;
  411. if (!select_node) // Paranoia
  412. {
  413. continue;
  414. }
  415. LLViewerObject* object = select_node->getObject();
  416. if (!object) // Paranoia bis
  417. {
  418. continue;
  419. }
  420. LLVector3 position = object->getPositionAgent();
  421. LLBBox bbox(position, LLQuaternion(), LLVector3::zero,
  422. LLVector3::zero);
  423. bbox.addPointLocal(LLVector3::zero);
  424. // Add the parent's bbox
  425. bbox.addBBoxAgent(object->getBoundingBoxAgent());
  426. typedef LLViewerObject::const_child_list_t child_list_t;
  427. const child_list_t& children = object->getChildren();
  428. for (child_list_t::const_iterator i = children.begin(),
  429. e = children.end();
  430. i != e; ++i)
  431. {
  432. // Add the child's bbox
  433. LLViewerObject* child = *i;
  434. bbox.addBBoxAgent(child->getBoundingBoxAgent());
  435. }
  436. objects.push_back(object);
  437. original_bboxes[object] = bbox;
  438. }
  439. S32 axis = mHighlightedAxis;
  440. F32 direction = mHighlightedDirection;
  441. // Sort them into positional order for proper packing
  442. BBoxCompare compare(axis, direction, original_bboxes);
  443. sort(objects.begin(), objects.end(), compare);
  444. // Storage for their new position after alignment; start with original
  445. // position first
  446. bbox_map_t new_bboxes = original_bboxes;
  447. // Find new positions
  448. for (S32 i = 0, count = objects.size(); i < count; ++i)
  449. {
  450. LLVector3 target_corner = mBBox.getCenterAgent() -
  451. direction * mBBox.getExtentLocal() * 0.5f;
  452. LLViewerObject* object = objects[i];
  453. const LLBBox& this_bbox = original_bboxes[object];
  454. LLVector3 this_corner = this_bbox.getCenterAgent() -
  455. direction * this_bbox.getExtentLocal() * 0.5f;
  456. // For packing, we cycle over several possible positions, taking the
  457. // smallest that does not overlap
  458. // 999999 guaranteed not to be the smallest
  459. F32 smallest = direction * 9999999;
  460. for (S32 j = 0; j <= i; ++j)
  461. {
  462. // Now far must it move?
  463. LLVector3 delta = target_corner - this_corner;
  464. // New position moves only on one axis, please
  465. LLVector3 delta_one_axis = LLVector3(0, 0, 0);
  466. delta_one_axis.mV[axis] = delta.mV[axis];
  467. LLVector3 new_position =
  468. this_bbox.getCenterAgent() + delta_one_axis;
  469. // Construct the new bbox
  470. LLBBox new_bbox(new_position, LLQuaternion(), LLVector3::zero,
  471. LLVector3::zero);
  472. new_bbox.addPointLocal(this_bbox.getExtentLocal() * 0.5f);
  473. new_bbox.addPointLocal(this_bbox.getExtentLocal() * -0.5f);
  474. // Check to see if it overlaps the previously placed objects
  475. bool overlap = false;
  476. LL_DEBUGS("ToolAlign") << "i=" << i << " j=" << j << LL_ENDL;
  477. if (!mForce) // Well, do not check if in force mode
  478. {
  479. for (S32 k = 0; k < i; ++k)
  480. {
  481. LLViewerObject* other_object = objects[k];
  482. LLBBox other_bbox = new_bboxes[other_object];
  483. bool overlaps_this = bbox_overlap(other_bbox, new_bbox);
  484. if (overlaps_this)
  485. {
  486. LL_DEBUGS("ToolAlign") << "Overlap: "
  487. << new_bbox.getCenterAgent()
  488. << " / "
  489. << other_bbox.getCenterAgent()
  490. << " - Extent: "
  491. << new_bbox.getExtentLocal()
  492. << " / "
  493. << other_bbox.getExtentLocal()
  494. << LL_ENDL;
  495. }
  496. overlap |= overlaps_this;
  497. }
  498. }
  499. if (!overlap)
  500. {
  501. F32 this_value = (new_bbox.getCenterAgent() -
  502. direction *
  503. new_bbox.getExtentLocal() * 0.5f).mV[axis];
  504. if (direction * this_value < direction * smallest)
  505. {
  506. smallest = this_value;
  507. // Store it
  508. new_bboxes[object] = new_bbox;
  509. }
  510. }
  511. // Update target for next time through the loop
  512. if (j < (S32)objects.size())
  513. {
  514. LLBBox next_bbox = new_bboxes[objects[j]];
  515. target_corner = next_bbox.getCenterAgent() +
  516. direction * next_bbox.getExtentLocal() * 0.5f;
  517. }
  518. }
  519. }
  520. // Now move them
  521. LLVector3 delta;
  522. for (S32 i = 0, count = objects.size(); i < count; ++i)
  523. {
  524. LLViewerObject* object = objects[i];
  525. const LLBBox& original_bbox = original_bboxes[object];
  526. const LLBBox& new_bbox = new_bboxes[object];
  527. delta = new_bbox.getCenterAgent() - original_bbox.getCenterAgent();
  528. object->setPositionLocal(object->getPositionAgent() + delta);
  529. }
  530. gSelectMgr.sendMultipleUpdate(UPD_POSITION);
  531. }