Merge branch 'master' into llwebprofile

This commit is contained in:
Aleric Inglewood
2012-11-28 18:50:31 +01:00
10 changed files with 216 additions and 147 deletions

27
README
View File

@@ -9,15 +9,30 @@
Sin-gu-la-ri-ty (noun) - a distinctive feature, a uniqueness; a point at which
continuity breaks up; a point in history at which machine becomes smarter than
humanity and/or fuses with it indivisively; or simply a cool sounding word with
my initials in it :)
the initials S.G. in it :)
Singularity Viewer is a Second Life protocol compatible client application. It
can be used to access Second LIfe service as well as a number of other such as
those based upon OpenSim plattform. It is directly based upon source code of
Ascent Viewer by Balseraph Software Group, which is in turn based upon source
code released by Linden Lab, with contributions from various sources.
Singularity Viewer is a SecondLife(tm) protocol compatible client application.
It can be used to access SecondLife services as well as a number of others such
as those based upon the OpenSim platform.
Singulariy is maintained by a small group of volunteers who can be contacted
both, in-world (SingularityViewer group) as well on IRC (#SingularityViewer
@ FreeNode). Bug requests and features requests can be submitted through our
Issue Tracket (http://code.google.com/p/singularity-viewer/issues/list or from
the viewer menu: Help --> Bug Reporting --> Singularity Issue Tracker...)
As this Readme grows out of date, please refer to
http://www.singularityviewer.org/about
00000000011111111112222222222333333333344444444445555555555666666666677777777778
12345678901234567890123456789012345678901234567890123456789012345678901234567890
History
The Singularity viewer was started by Siana Gearz in November 2010 by forking it
of the Ascent Viewer, by Balseraph Software Group, which in turn was based upon
source code released by Linden Lab.

View File

@@ -248,6 +248,7 @@ Celierra Darling
VWR-6975
Cron Stardust
VWR-10579
STORM-1919
Cypren Christenson
SNOW-129
SNOW-140

View File

@@ -418,6 +418,7 @@ void cleanupCurl(void)
if (CurlMultiHandle::getTotalMultiHandles() != 0)
llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl;
AIStateMachine::flush();
clearCommandQueue();
Stats::print();
ssl_cleanup();
@@ -491,8 +492,6 @@ void Stats::print(void)
// Even more strict, BufferedCurlEasyRequest may not be created directly either, only as
// base class of ThreadSafeBufferedCurlEasyRequest.
llassert(BufferedCurlEasyRequest_count == ThreadSafeBufferedCurlEasyRequest_count);
// Each AICurlEasyRequestStateMachine is responsible for exactly one easy handle.
llassert(easy_handles >= AICurlEasyRequest_count);
// Each AICurlEasyRequestStateMachine has one AICurlEasyRequest member.
llassert(AICurlEasyRequest_count >= AICurlEasyRequestStateMachine_count);
// AIFIXME: is this really always the case? And why?
@@ -1148,12 +1147,16 @@ void CurlEasyRequest::set_timeout_opts(void)
setopt(CURLOPT_TIMEOUT, mTimeoutPolicy->getCurlTransaction());
}
void CurlEasyRequest::create_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj)
void CurlEasyRequest::create_timeout_object(void)
{
ThreadSafeBufferedCurlEasyRequest* lockobj = NULL;
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
lockobj = static_cast<BufferedCurlEasyRequest*>(this)->get_lockobj();
#endif
mTimeout = new curlthread::HTTPTimeout(mTimeoutPolicy, lockobj);
}
LLPointer<curlthread::HTTPTimeout>& CurlEasyRequest::get_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj)
LLPointer<curlthread::HTTPTimeout>& CurlEasyRequest::get_timeout_object(void)
{
if (mTimeoutIsOrphan)
{
@@ -1162,7 +1165,7 @@ LLPointer<curlthread::HTTPTimeout>& CurlEasyRequest::get_timeout_object(ThreadSa
}
else
{
create_timeout_object(lockobj);
create_timeout_object();
}
return mTimeout;
}

View File

@@ -56,12 +56,17 @@ PerHostRequestQueuePtr PerHostRequestQueue::instance(std::string const& hostname
//static
void PerHostRequestQueue::release(PerHostRequestQueuePtr& instance)
{
if (instance->lastone())
if (instance->exactly_two_left()) // Being 'instance' and the one in sInstanceMap.
{
// The viewer can be have left main() we can't access the global sInstanceMap anymore.
if (LLApp::isStopped())
{
return;
}
instance_map_wat instance_map_w(sInstanceMap);
// It is possible that 'lastone' is not up to date anymore.
// It is possible that 'exactly_two_left' is not up to date anymore.
// Therefore, recheck the condition now that we have locked sInstanceMap.
if (!instance->lastone())
if (!instance->exactly_two_left())
{
// Some other thread added this host in the meantime.
return;

View File

@@ -118,7 +118,7 @@ class PerHostRequestQueue {
class RefCountedThreadSafePerHostRequestQueue : public threadsafe_PerHostRequestQueue {
public:
RefCountedThreadSafePerHostRequestQueue(void) : mReferenceCount(0) { }
bool lastone(void) const { llassert(mReferenceCount >= 2); return mReferenceCount == 2; }
bool exactly_two_left(void) const { return mReferenceCount == 2; }
private:
// Used by PerHostRequestQueuePtr. Object is deleted when reference count reaches zero.

View File

@@ -119,6 +119,7 @@ inline CURLMcode check_multi_code(CURLMcode code) { AICurlInterface::Stats::mult
bool curlThreadIsRunning(void);
void wakeUpCurlThread(void);
void stopCurlThread(void);
void clearCommandQueue(void);
#define DECLARE_SETOPT(param_type) \
CURLcode setopt(CURLoption option, param_type parameter)
@@ -316,7 +317,7 @@ class CurlEasyRequest : public CurlEasyHandle {
static CURLcode curlCtxCallback(CURL* curl, void* sslctx, void* parm);
// Called from get_timeout_object and httptimeout.
void create_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj);
void create_timeout_object(void);
public:
// Set default options that we want applied to all curl easy handles.
@@ -374,9 +375,9 @@ class CurlEasyRequest : public CurlEasyHandle {
std::string const& getLowercaseHostname(void) const { return mLowercaseHostname; }
// Called by CurlSocketInfo to allow access to the last (after a redirect) HTTPTimeout object related to this request.
// This creates mTimeout (unless mTimeoutIsOrphan is set in which case it adopts the orphan).
LLPointer<curlthread::HTTPTimeout>& get_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj);
LLPointer<curlthread::HTTPTimeout>& get_timeout_object(void);
// Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL).
LLPointer<curlthread::HTTPTimeout>& httptimeout(ThreadSafeBufferedCurlEasyRequest* lockobj = NULL) { if (lockobj && !mTimeout) create_timeout_object(lockobj); return mTimeout; }
LLPointer<curlthread::HTTPTimeout>& httptimeout(void) { if (!mTimeout) { create_timeout_object(); mTimeoutIsOrphan = true; } return mTimeout; }
// Return true if no data has been received on the latest socket (if any) for too long.
bool has_stalled(void) const { return mTimeout && mTimeout->has_stalled(); }

View File

@@ -775,7 +775,7 @@ CurlSocketInfo::CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socke
// CurlSocketInfo objects for a request and we need upload_finished() to be called on the HTTPTimeout
// object related to THIS CurlSocketInfo.
AICurlEasyRequest_wat easy_request_w(*lockobj);
mTimeout = easy_request_w->get_timeout_object(lockobj);
mTimeout = easy_request_w->get_timeout_object();
}
CurlSocketInfo::~CurlSocketInfo()
@@ -1065,6 +1065,10 @@ void AICurlThread::wakeup_thread(void)
DoutEntering(dc::curl, "AICurlThread::wakeup_thread");
llassert(is_main_thread());
// If we are already exiting the viewer then return immediately.
if (!mRunning)
return;
// Try if curl thread is still awake and if so, pass the new commands directly.
if (mWakeUpMutex.tryLock())
{
@@ -1609,7 +1613,9 @@ CURLMcode MultiHandle::remove_easy_request(addedEasyRequests_type::iterator cons
ThreadSafeBufferedCurlEasyRequest* lockobj = iter->get_ptr().get();
#endif
mAddedEasyRequests.erase(iter);
#if CWDEBUG
Dout(dc::curl, "MultiHandle::remove_easy_request: Removed AICurlEasyRequest " << (void*)lockobj << "; now processing " << mAddedEasyRequests.size() << " easy handles.");
#endif
// Attempt to add a queued request, if any.
PerHostRequestQueue_wat(*per_host)->add_queued_to(this);
@@ -1971,6 +1977,10 @@ void HTTPTimeout::done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode
DoutCurl("done: mStalled set to -1");
}
// Libcurl uses GetTickCount on windows, with a resolution of 10 to 16 ms.
// As a result, we can not assume that namelookup_time == 0 has a special meaning.
#define LOWRESTIMER LL_WINDOWS
void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, char const* eff_url)
{
llwarns << "Request to \"" << curl_easy_request->getLowercaseHostname() << "\" timed out for " << curl_easy_request->getTimeoutPolicy()->name() << llendl;
@@ -1981,8 +1991,15 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
curl_easy_request->getinfo(CURLINFO_APPCONNECT_TIME, &appconnect_time);
curl_easy_request->getinfo(CURLINFO_PRETRANSFER_TIME, &pretransfer_time);
curl_easy_request->getinfo(CURLINFO_STARTTRANSFER_TIME, &starttransfer_time);
if (namelookup_time == 0)
if (namelookup_time == 0
#if LOWRESTIMER
&& connect_time == 0
#endif
)
{
#if LOWRESTIMER
llinfos << "Hostname seems to have been still in the DNS cache." << llendl;
#else
llwarns << "Huh? Curl returned CURLE_OPERATION_TIMEDOUT, but DNS lookup did not occur according to timings. Expected CURLE_COULDNT_RESOLVE_PROXY or CURLE_COULDNT_RESOLVE_HOST!" << llendl;
llassert(connect_time == 0);
llassert(appconnect_time == 0);
@@ -1990,17 +2007,26 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
llassert(starttransfer_time == 0);
// Fatal error for diagnostics.
return;
#endif
}
// If namelookup_time is less than 500 microseconds, then it's very likely just a DNS cache lookup.
else if (namelookup_time < 500e-6)
{
#if LOWRESTIMER
llinfos << "Hostname was most likely still in DNS cache (or lookup occured in under ~10ms)." << llendl;
#else
llinfos << "Hostname was still in DNS cache." << llendl;
#endif
}
else
{
llwarns << "DNS lookup of " << curl_easy_request->getLowercaseHostname() << " took " << namelookup_time << " seconds." << llendl;
llinfos << "DNS lookup of " << curl_easy_request->getLowercaseHostname() << " took " << namelookup_time << " seconds." << llendl;
}
if (connect_time == 0)
if (connect_time == 0
#if LOWRESTIMER
&& namelookup_time > 0 // connect_time, when set, is namelookup_time + something.
#endif
)
{
llwarns << "Huh? Curl returned CURLE_OPERATION_TIMEDOUT, but connection did not occur according to timings. Expected CURLE_COULDNT_CONNECT!" << llendl;
llassert(appconnect_time == 0);
@@ -2010,16 +2036,21 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
return;
}
// If connect_time is almost equal to namelookup_time, then it was just set because it was already connected.
if (connect_time - namelookup_time <= 1e-6)
if (connect_time - namelookup_time <= 1e-5)
{
#if LOWRESTIMER // Assuming 10ms resolution.
llinfos << "The socket was most likely already connected (or you connected to a proxy with a connect time of under ~10 ms)." << llendl;
#else
llinfos << "The socket was already connected (to remote or proxy)." << llendl;
#endif
// I'm assuming that the SSL/TLS handshake can be measured with a low res timer.
if (appconnect_time == 0)
{
llwarns << "The SSL/TLS handshake never occurred according to the timings!" << llendl;
return;
}
// If appconnect_time is almost equal to connect_time, then it was just set because this is a connection re-use.
if (appconnect_time - connect_time <= 1e-6)
if (appconnect_time - connect_time <= 1e-5)
{
llinfos << "Connection with HTTP server was already established; this was a re-used connection." << llendl;
}
@@ -2043,7 +2074,7 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
llwarns << "The transfer never happened because there was too much in the pipeline (apparently)." << llendl;
return;
}
else if (pretransfer_time - appconnect_time >= 1e-6)
else if (pretransfer_time - appconnect_time >= 1e-5)
{
llinfos << "Apparently there was a delay, due to waits in line for the pipeline, of " << (pretransfer_time - appconnect_time) << " seconds before the transfer began." << llendl;
}
@@ -2123,10 +2154,14 @@ void stopCurlThread(void)
ms_sleep(10);
}
Dout(dc::curl, "Curl thread" << (curlThreadIsRunning() ? " not" : "") << " stopped after " << ((100 - count) * 10) << "ms.");
// Clear the command queue, for a cleaner cleanup.
}
}
void clearCommandQueue(void)
{
// Clear the command queue now in order to avoid the global deinitialization order fiasco.
command_queue_wat command_queue_w(command_queue);
command_queue_w->clear();
}
}
//-----------------------------------------------------------------------------
@@ -2240,11 +2275,8 @@ size_t BufferedCurlEasyRequest::curlReadCallback(char* data, size_t size, size_t
S32 bytes = size * nmemb; // The maximum amount to read.
self_w->mLastRead = self_w->getInput()->readAfter(sChannels.out(), self_w->mLastRead, (U8*)data, bytes);
self_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server.
// Timeout administration. Note that it can happen that we get here
// before the socket callback has been called, because the silly libcurl
// writes headers without informing us. In that case it's OK to create
// the Timeout object on the fly, so pass lockobj.
if (self_w->httptimeout(lockobj)->data_sent(bytes))
// Timeout administration.
if (self_w->httptimeout()->data_sent(bytes))
{
// Transfer timed out. Return CURL_READFUNC_ABORT which will abort with error CURLE_ABORTED_BY_CALLBACK.
return CURL_READFUNC_ABORT;
@@ -2337,10 +2369,10 @@ size_t BufferedCurlEasyRequest::curlHeaderCallback(char* data, size_t size, size
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
int debug_callback(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr)
{
BufferedCurlEasyRequest* request = (BufferedCurlEasyRequest*)user_ptr;
#ifdef CWDEBUG
using namespace ::libcwd;
BufferedCurlEasyRequest* request = (BufferedCurlEasyRequest*)user_ptr;
std::ostringstream marker;
marker << (void*)request->get_lockobj();
libcw_do.push_marker();

View File

@@ -624,7 +624,7 @@ void debug_curl_easy_reset(CURL* handle)
CURLcode debug_curl_easy_setopt(CURL* handle, CURLoption option, ...)
{
CURLcode ret;
CURLcode ret = CURLE_UNKNOWN_OPTION; // Suppress compiler warning.
va_list ap;
union param_type {
long along;
@@ -841,7 +841,7 @@ CURLMcode debug_curl_multi_remove_handle(CURLM* multi_handle, CURL* easy_handle)
CURLMcode debug_curl_multi_setopt(CURLM* multi_handle, CURLMoption option, ...)
{
CURLMcode ret;
CURLMcode ret = CURLM_UNKNOWN_OPTION; // Suppress compiler warning.
va_list ap;
union param_type {
long along;

View File

@@ -71,7 +71,7 @@ void LLStyle::drawControl(ControlElement element, const QStyleOption *option, QP
QPlastiqueStyle::drawControl(element, &localOption, painter, widget);
return;
}
default:
break;
}

View File

@@ -1381,74 +1381,28 @@ LLQuaternion LLManipRotate::dragConstrained( S32 x, S32 y )
BOOL hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis);
projected_mouse -= snap_plane_center;
S32 snap_plane = 0;
F32 dot = cam_to_snap_plane * constraint_axis;
if (llabs(dot) < 0.01f)
{
// looking at ring edge on, project onto view plane and check if mouse is past ring
getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane);
projected_mouse -= snap_plane_center;
dot = projected_mouse * constraint_axis;
if (projected_mouse * constraint_axis > 0)
{
snap_plane = 1;
}
projected_mouse -= dot * constraint_axis;
}
else if (dot > 0.f)
{
// look for mouse position outside and in front of snap circle
if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f)
{
snap_plane = 1;
}
}
else
{
// look for mouse position inside or in back of snap circle
if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit)
{
snap_plane = 1;
}
}
if (snap_plane == 0)
{
// try other plane
snap_plane_center = (center - (constraint_axis * mRadiusMeters * 0.5f));
if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
{
cam_to_snap_plane.setVec(1.f, 0.f, 0.f);
}
else
{
cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent();
cam_to_snap_plane.normVec();
}
hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis);
projected_mouse -= snap_plane_center;
dot = cam_to_snap_plane * constraint_axis;
if (gSavedSettings.getBOOL("SnapEnabled")) {
S32 snap_plane = 0;
F32 dot = cam_to_snap_plane * constraint_axis;
if (llabs(dot) < 0.01f)
{
// looking at ring edge on, project onto view plane and check if mouse is past ring
getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane);
projected_mouse -= snap_plane_center;
dot = projected_mouse * constraint_axis;
if (projected_mouse * constraint_axis < 0)
if (projected_mouse * constraint_axis > 0)
{
snap_plane = 2;
snap_plane = 1;
}
projected_mouse -= dot * constraint_axis;
}
else if (dot < 0.f)
else if (dot > 0.f)
{
// look for mouse position outside and in front of snap circle
if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f)
{
snap_plane = 2;
snap_plane = 1;
}
}
else
@@ -1456,78 +1410,136 @@ LLQuaternion LLManipRotate::dragConstrained( S32 x, S32 y )
// look for mouse position inside or in back of snap circle
if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit)
{
snap_plane = 2;
snap_plane = 1;
}
}
}
if (snap_plane > 0)
{
LLVector3 cam_at_axis;
if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
if (snap_plane == 0)
{
cam_at_axis.setVec(1.f, 0.f, 0.f);
// try other plane
snap_plane_center = (center - (constraint_axis * mRadiusMeters * 0.5f));
if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
{
cam_to_snap_plane.setVec(1.f, 0.f, 0.f);
}
else
{
cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent();
cam_to_snap_plane.normVec();
}
hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis);
projected_mouse -= snap_plane_center;
dot = cam_to_snap_plane * constraint_axis;
if (llabs(dot) < 0.01f)
{
// looking at ring edge on, project onto view plane and check if mouse is past ring
getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane);
projected_mouse -= snap_plane_center;
dot = projected_mouse * constraint_axis;
if (projected_mouse * constraint_axis < 0)
{
snap_plane = 2;
}
projected_mouse -= dot * constraint_axis;
}
else if (dot < 0.f)
{
// look for mouse position outside and in front of snap circle
if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f)
{
snap_plane = 2;
}
}
else
{
// look for mouse position inside or in back of snap circle
if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit)
{
snap_plane = 2;
}
}
}
else
{
cam_at_axis = snap_plane_center - gAgentCamera.getCameraPositionAgent();
cam_at_axis.normVec();
}
// first, project mouse onto screen plane at point tangent to rotation radius.
getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_at_axis);
// project that point onto rotation plane
projected_mouse -= snap_plane_center;
projected_mouse -= projected_vec(projected_mouse, constraint_axis);
F32 mouse_lateral_dist = llmin(SNAP_GUIDE_INNER_RADIUS * mRadiusMeters, projected_mouse.magVec());
F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters;
if (llabs(mouse_lateral_dist) > 0.01f)
{
mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) -
(mouse_lateral_dist * mouse_lateral_dist));
}
LLVector3 projected_camera_at = cam_at_axis - projected_vec(cam_at_axis, constraint_axis);
projected_mouse -= mouse_depth * projected_camera_at;
if (!mInSnapRegime)
{
mSmoothRotate = TRUE;
}
mInSnapRegime = TRUE;
// 0 to 360 deg
F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f);
F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT);
//fmodf(llround(mouse_angle * RAD_TO_DEG, 7.5f) + 360.f, 360.f);
LLVector3 object_axis;
getObjectAxisClosestToMouse(object_axis);
object_axis = object_axis * first_object_node->mSavedRotation;
// project onto constraint plane
object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis();
object_axis.normVec();
if (relative_mouse_angle < SNAP_ANGLE_DETENTE)
if (snap_plane > 0)
{
F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f));
angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2);
LLVector3 cam_at_axis;
if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
{
cam_at_axis.setVec(1.f, 0.f, 0.f);
}
else
{
cam_at_axis = snap_plane_center - gAgentCamera.getCameraPositionAgent();
cam_at_axis.normVec();
}
// first, project mouse onto screen plane at point tangent to rotation radius.
getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_at_axis);
// project that point onto rotation plane
projected_mouse -= snap_plane_center;
projected_mouse -= projected_vec(projected_mouse, constraint_axis);
F32 mouse_lateral_dist = llmin(SNAP_GUIDE_INNER_RADIUS * mRadiusMeters, projected_mouse.magVec());
F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters;
if (llabs(mouse_lateral_dist) > 0.01f)
{
mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) -
(mouse_lateral_dist * mouse_lateral_dist));
}
LLVector3 projected_camera_at = cam_at_axis - projected_vec(cam_at_axis, constraint_axis);
projected_mouse -= mouse_depth * projected_camera_at;
if (!mInSnapRegime)
{
mSmoothRotate = TRUE;
}
mInSnapRegime = TRUE;
// 0 to 360 deg
F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f);
F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT);
//fmodf(llround(mouse_angle * RAD_TO_DEG, 7.5f) + 360.f, 360.f);
LLVector3 object_axis;
getObjectAxisClosestToMouse(object_axis);
object_axis = object_axis * first_object_node->mSavedRotation;
// project onto constraint plane
object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis();
object_axis.normVec();
if (relative_mouse_angle < SNAP_ANGLE_DETENTE)
{
F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f));
angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2);
}
else
{
angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2);
}
return LLQuaternion( -angle, constraint_axis );
}
else
{
angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2);
if (mInSnapRegime)
{
mSmoothRotate = TRUE;
}
mInSnapRegime = FALSE;
}
return LLQuaternion( -angle, constraint_axis );
}
else
{
else {
if (mInSnapRegime)
{
mSmoothRotate = TRUE;
}
mInSnapRegime = FALSE;
}
if (!mInSnapRegime)
{
LLVector3 up_from_axis = mCenterToCamNorm % constraint_axis;
up_from_axis.normVec();
LLVector3 cur_intersection;