DRTVWR-534: Batch of modifications to 360 capture project after moving from internal repo to public one.
parent
c40b8310b0
commit
0c89e67eb5
|
|
@ -56,6 +56,7 @@ indra/newview/search_history.txt
|
|||
indra/newview/teleport_history.txt
|
||||
indra/newview/typed_locations.txt
|
||||
indra/newview/vivox-runtime
|
||||
indra/newview/skins/default/html/common/equirectangular/js
|
||||
indra/server-linux-*
|
||||
indra/temp
|
||||
indra/test/linden_file.dat
|
||||
|
|
|
|||
156
autobuild.xml
156
autobuild.xml
|
|
@ -367,6 +367,58 @@
|
|||
<key>version</key>
|
||||
<string>2.3.545362</string>
|
||||
</map>
|
||||
<key>cubemaptoequirectangular</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
<string>Copyright (c) 2017 Jaume Sanchez Elias, http://www.clicktorelease.com</string>
|
||||
<key>license</key>
|
||||
<string>MIT</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/CUBEMAPTOEQUIRECTANGULAR_LICENSE.txt</string>
|
||||
<key>name</key>
|
||||
<string>cubemaptoequirectangular</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>e6da43ea831b2416a5a8b388a511e70b</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85446/792080/cubemaptoequirectangular-1.1.0-darwin64-562268.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>f26636666a056cacea0618bc2648d935</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85451/792109/cubemaptoequirectangular-1.1.0-windows-562268.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>a59ba299bc94e915a8b55e9eef681253</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85449/792108/cubemaptoequirectangular-1.1.0-windows64-562268.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1.1.0</string>
|
||||
</map>
|
||||
<key>curl</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
|
|
@ -1491,6 +1543,58 @@
|
|||
<key>version</key>
|
||||
<string>2012.1-2</string>
|
||||
</map>
|
||||
<key>jpegencoderbasic</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
<string>Andreas Ritter, www.bytestrom.eu, 11/2009</string>
|
||||
<key>license</key>
|
||||
<string>NONE</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/JPEG_ENCODER_BASIC_LICENSE.txt</string>
|
||||
<key>name</key>
|
||||
<string>jpegencoderbasic</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>d07453bf10bae71013209874477640bb</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85447/792085/jpegencoderbasic-1.0-darwin64-562269.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>e422fee0c82507ec218597654f8cadbf</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85450/792107/jpegencoderbasic-1.0-windows-562269.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>fbe07040cecc4e8760acc2cdf2436468</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85448/792106/jpegencoderbasic-1.0-windows64-562269.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1.0</string>
|
||||
</map>
|
||||
<key>jpeglib</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
|
|
@ -3067,6 +3171,58 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>version</key>
|
||||
<string>4.10.0000.32327.5fc3fe7c.539691</string>
|
||||
</map>
|
||||
<key>threejs</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
<string>Copyright © 2010-2021 three.js authors</string>
|
||||
<key>license</key>
|
||||
<string>MIT</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/THREEJS_LICENSE.txt</string>
|
||||
<key>name</key>
|
||||
<string>threejs</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>313a852ddc87be24235319987a42f0f2</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86208/797009/threejs-0.131.3-darwin64-562835.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>850b2400f91e7704653fe3fd4171ce94</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86210/797031/threejs-0.131.3-windows-562835.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>821f398169c1743987ac1b99376f767f</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86209/797030/threejs-0.131.3-windows64-562835.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>0.131.3</string>
|
||||
</map>
|
||||
<key>tut</key>
|
||||
<map>
|
||||
<key>copyright</key>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- cmake -*-
|
||||
use_prebuilt_binary(cubemaptoequirectangular)
|
||||
|
||||
# Main JS file
|
||||
configure_file("${AUTOBUILD_INSTALL_DIR}/js/CubemapToEquirectangular.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/CubemapToEquirectangular.js" COPYONLY)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- cmake -*-
|
||||
use_prebuilt_binary(jpegencoderbasic)
|
||||
|
||||
# Main JS file
|
||||
configure_file("${AUTOBUILD_INSTALL_DIR}/js/jpeg_encoder_basic.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/jpeg_encoder_basic.js" COPYONLY)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# -*- cmake -*-
|
||||
use_prebuilt_binary(threejs)
|
||||
|
||||
# Main three.js file
|
||||
configure_file("${AUTOBUILD_INSTALL_DIR}/js/three.min.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/three.min.js" COPYONLY)
|
||||
|
||||
# Controls to move around the scene using mouse or keyboard
|
||||
configure_file("${AUTOBUILD_INSTALL_DIR}/js/OrbitControls.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/OrbitControls.js" COPYONLY)
|
||||
|
|
@ -714,6 +714,15 @@ void LLPluginClassMedia::loadURI(const std::string &uri)
|
|||
sendMessage(message);
|
||||
}
|
||||
|
||||
void LLPluginClassMedia::executeJavaScript(const std::string &code)
|
||||
{
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "execute_javascript");
|
||||
|
||||
message.setValue("code", code);
|
||||
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
const char* LLPluginClassMedia::priorityToString(EPriority priority)
|
||||
{
|
||||
const char* result = "UNKNOWN";
|
||||
|
|
@ -891,6 +900,19 @@ void LLPluginClassMedia::setJavascriptEnabled(const bool enabled)
|
|||
sendMessage(message);
|
||||
}
|
||||
|
||||
void LLPluginClassMedia::setWebSecurityDisabled(const bool disabled)
|
||||
{
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "web_security_disabled");
|
||||
message.setValueBoolean("disabled", disabled);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
void LLPluginClassMedia::setFileAccessFromFileUrlsEnabled(const bool enabled)
|
||||
{
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "file_access_from_file_urls");
|
||||
message.setValueBoolean("enabled", enabled);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
void LLPluginClassMedia::enableMediaPluginDebugging( bool enable )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ public:
|
|||
|
||||
void loadURI(const std::string &uri);
|
||||
|
||||
void executeJavaScript(const std::string &code);
|
||||
|
||||
// "Loading" means uninitialized or any state prior to fully running (processing commands)
|
||||
bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; };
|
||||
|
||||
|
|
@ -199,6 +201,8 @@ public:
|
|||
void setLanguageCode(const std::string &language_code);
|
||||
void setPluginsEnabled(const bool enabled);
|
||||
void setJavascriptEnabled(const bool enabled);
|
||||
void setWebSecurityDisabled(const bool disabled);
|
||||
void setFileAccessFromFileUrlsEnabled(const bool enabled);
|
||||
void setTarget(const std::string &target);
|
||||
|
||||
///////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ private:
|
|||
void onTooltipCallback(std::string text);
|
||||
void onLoadStartCallback();
|
||||
void onRequestExitCallback();
|
||||
void onLoadEndCallback(int httpStatusCode);
|
||||
void onLoadEndCallback(int httpStatusCode, std::string url);
|
||||
void onLoadError(int status, const std::string error_text);
|
||||
void onAddressChangeCallback(std::string url);
|
||||
void onOpenPopupCallback(std::string url, std::string target);
|
||||
|
|
@ -92,6 +92,8 @@ private:
|
|||
bool mDisableGPU;
|
||||
bool mDisableNetworkService;
|
||||
bool mUseMockKeyChain;
|
||||
bool mDisableWebSecurity;
|
||||
bool mFileAccessFromFileUrls;
|
||||
std::string mUserAgentSubtring;
|
||||
std::string mAuthUsername;
|
||||
std::string mAuthPassword;
|
||||
|
|
@ -127,6 +129,8 @@ MediaPluginBase(host_send_func, host_user_data)
|
|||
mDisableGPU = false;
|
||||
mDisableNetworkService = true;
|
||||
mUseMockKeyChain = true;
|
||||
mDisableWebSecurity = false;
|
||||
mFileAccessFromFileUrls = false;
|
||||
mUserAgentSubtring = "";
|
||||
mAuthUsername = "";
|
||||
mAuthPassword = "";
|
||||
|
|
@ -260,13 +264,14 @@ void MediaPluginCEF::onRequestExitCallback()
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
void MediaPluginCEF::onLoadEndCallback(int httpStatusCode)
|
||||
void MediaPluginCEF::onLoadEndCallback(int httpStatusCode, std::string url)
|
||||
{
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete");
|
||||
//message.setValue("uri", event.getEventUri()); // not easily available here in CEF - needed?
|
||||
message.setValueS32("result_code", httpStatusCode);
|
||||
message.setValueBoolean("history_back_available", mCEFLib->canGoBack());
|
||||
message.setValueBoolean("history_forward_available", mCEFLib->canGoForward());
|
||||
message.setValue("uri", url);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
|
|
@ -355,14 +360,16 @@ const std::vector<std::string> MediaPluginCEF::onFileDialog(dullahan::EFileDialo
|
|||
}
|
||||
else if (dialog_type == dullahan::FD_SAVE_FILE)
|
||||
{
|
||||
mPickedFiles.clear();
|
||||
mAuthOK = false;
|
||||
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "file_download");
|
||||
message.setValueBoolean("blocking_request", true);
|
||||
message.setValue("filename", default_file);
|
||||
|
||||
sendMessage(message);
|
||||
|
||||
return std::vector<std::string>();
|
||||
return mPickedFiles;
|
||||
}
|
||||
|
||||
return std::vector<std::string>();
|
||||
|
|
@ -523,7 +530,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
|
|||
mCEFLib->setOnTitleChangeCallback(std::bind(&MediaPluginCEF::onTitleChangeCallback, this, std::placeholders::_1));
|
||||
mCEFLib->setOnTooltipCallback(std::bind(&MediaPluginCEF::onTooltipCallback, this, std::placeholders::_1));
|
||||
mCEFLib->setOnLoadStartCallback(std::bind(&MediaPluginCEF::onLoadStartCallback, this));
|
||||
mCEFLib->setOnLoadEndCallback(std::bind(&MediaPluginCEF::onLoadEndCallback, this, std::placeholders::_1));
|
||||
mCEFLib->setOnLoadEndCallback(std::bind(&MediaPluginCEF::onLoadEndCallback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
mCEFLib->setOnLoadErrorCallback(std::bind(&MediaPluginCEF::onLoadError, this, std::placeholders::_1, std::placeholders::_2));
|
||||
mCEFLib->setOnAddressChangeCallback(std::bind(&MediaPluginCEF::onAddressChangeCallback, this, std::placeholders::_1));
|
||||
mCEFLib->setOnOpenPopupCallback(std::bind(&MediaPluginCEF::onOpenPopupCallback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
|
@ -562,6 +569,19 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
|
|||
settings.disable_network_service = mDisableNetworkService;
|
||||
settings.use_mock_keychain = mUseMockKeyChain;
|
||||
#endif
|
||||
// these were added to facilitate loading images directly into a local
|
||||
// web page for the prototype 360 project in 2017 - something that is
|
||||
// disallowed normally by the browser security model. Now the the source
|
||||
// (cubemap) images are stores as JavaScript, we can avoid opening up
|
||||
// this security hole (it was only set for the 360 floater but still
|
||||
// a concern). Leaving them here, explicitly turn off vs removing
|
||||
// entirely from this source file so that others are aware of them
|
||||
// in the future.
|
||||
settings.disable_web_security = false;
|
||||
settings.file_access_from_file_urls = false;
|
||||
|
||||
settings.flash_enabled = mPluginsEnabled;
|
||||
|
||||
// This setting applies to all plugins, not just Flash
|
||||
// Regarding, SL-15559 PDF files do not load in CEF v91,
|
||||
// it turns out that on Windows, PDF support is treated
|
||||
|
|
@ -688,6 +708,11 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
|
|||
std::string uri = message_in.getValue("uri");
|
||||
mCEFLib->navigate(uri);
|
||||
}
|
||||
else if (message_name == "execute_javascript")
|
||||
{
|
||||
std::string code = message_in.getValue("code");
|
||||
mCEFLib->executeJavaScript(code);
|
||||
}
|
||||
else if (message_name == "set_cookie")
|
||||
{
|
||||
std::string uri = message_in.getValue("uri");
|
||||
|
|
@ -883,6 +908,14 @@ void MediaPluginCEF::receiveMessage(const char* message_string)
|
|||
{
|
||||
mDisableGPU = message_in.getValueBoolean("disable");
|
||||
}
|
||||
else if (message_name == "web_security_disabled")
|
||||
{
|
||||
mDisableWebSecurity = message_in.getValueBoolean("disabled");
|
||||
}
|
||||
else if (message_name == "file_access_from_file_urls")
|
||||
{
|
||||
mFileAccessFromFileUrls = message_in.getValueBoolean("enabled");
|
||||
}
|
||||
}
|
||||
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ include(bugsplat)
|
|||
include(BuildPackagesInfo)
|
||||
include(BuildVersion)
|
||||
include(CMakeCopyIfDifferent)
|
||||
include(CubemapToEquirectangularJS)
|
||||
include(DBusGlib)
|
||||
include(DragDrop)
|
||||
include(EXPAT)
|
||||
include(FMODSTUDIO)
|
||||
include(GLOD)
|
||||
include(Hunspell)
|
||||
include(JPEGEncoderBasic)
|
||||
include(JsonCpp)
|
||||
include(LLAppearance)
|
||||
include(LLAudio)
|
||||
|
|
@ -47,6 +49,7 @@ include(OpenGL)
|
|||
include(OpenSSL)
|
||||
include(PNG)
|
||||
include(TemplateCheck)
|
||||
include(ThreeJS)
|
||||
include(UI)
|
||||
include(UnixInstall)
|
||||
include(ViewerMiscLibs)
|
||||
|
|
@ -205,6 +208,7 @@ set(viewer_SOURCE_FILES
|
|||
llfilteredwearablelist.cpp
|
||||
llfirstuse.cpp
|
||||
llflexibleobject.cpp
|
||||
llfloater360capture.cpp
|
||||
llfloaterabout.cpp
|
||||
llfloaterbvhpreview.cpp
|
||||
llfloateraddpaymentmethod.cpp
|
||||
|
|
@ -278,7 +282,7 @@ set(viewer_SOURCE_FILES
|
|||
llfloaternamedesc.cpp
|
||||
llfloaternotificationsconsole.cpp
|
||||
llfloaternotificationstabbed.cpp
|
||||
llfloateroutfitphotopreview.cpp
|
||||
llfloateroutfitphotopreview.cpp
|
||||
llfloateroutfitsnapshot.cpp
|
||||
llfloaterobjectweights.cpp
|
||||
llfloateropenobject.cpp
|
||||
|
|
@ -844,6 +848,7 @@ set(viewer_HEADER_FILES
|
|||
llfilteredwearablelist.h
|
||||
llfirstuse.h
|
||||
llflexibleobject.h
|
||||
llfloater360capture.h
|
||||
llfloaterabout.h
|
||||
llfloaterbvhpreview.h
|
||||
llfloateraddpaymentmethod.h
|
||||
|
|
@ -1381,7 +1386,7 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt"
|
|||
"${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}\n")
|
||||
|
||||
set_source_files_properties(
|
||||
llversioninfo.cpp tests/llversioninfo_test.cpp
|
||||
llversioninfo.cpp tests/llversioninfo_test.cpp
|
||||
PROPERTIES
|
||||
COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake
|
||||
)
|
||||
|
|
@ -2004,7 +2009,7 @@ endif (WINDOWS)
|
|||
# one of these being libz where you can find four or more versions in play
|
||||
# at once. On Linux, libz can be found at link and run time via a number
|
||||
# of paths:
|
||||
#
|
||||
#
|
||||
# => -lfreetype
|
||||
# => libz.so.1 (on install machine, not build)
|
||||
# => -lSDL
|
||||
|
|
@ -2175,7 +2180,7 @@ if (DARWIN)
|
|||
|
||||
# https://blog.kitware.com/upcoming-in-cmake-2-8-12-osx-rpath-support/
|
||||
set(CMAKE_MACOSX_RPATH 1)
|
||||
|
||||
|
||||
set_target_properties(
|
||||
${VIEWER_BINARY_NAME}
|
||||
PROPERTIES
|
||||
|
|
@ -2436,7 +2441,7 @@ if (LL_TESTS)
|
|||
llworldmap.cpp
|
||||
llworldmipmap.cpp
|
||||
PROPERTIES
|
||||
LL_TEST_ADDITIONAL_SOURCE_FILES
|
||||
LL_TEST_ADDITIONAL_SOURCE_FILES
|
||||
tests/llviewertexture_stub.cpp
|
||||
#llviewertexturelist.cpp
|
||||
)
|
||||
|
|
@ -2470,7 +2475,7 @@ if (LL_TESTS)
|
|||
llworldmap.cpp
|
||||
llworldmipmap.cpp
|
||||
PROPERTIES
|
||||
LL_TEST_ADDITIONAL_SOURCE_FILES
|
||||
LL_TEST_ADDITIONAL_SOURCE_FILES
|
||||
tests/llviewertexture_stub.cpp
|
||||
#llviewertexturelist.cpp
|
||||
LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_SYSTEM_LIBRARY}"
|
||||
|
|
|
|||
|
|
@ -276,4 +276,15 @@
|
|||
is_running_function="Floater.IsOpen"
|
||||
is_running_parameters="my_environments"
|
||||
/>
|
||||
<command name="360capture"
|
||||
available_in_toybox="true"
|
||||
is_flashing_allowed="true"
|
||||
icon="Command_360_Capture_Icon"
|
||||
label_ref="Command_360_Capture_Label"
|
||||
tooltip_ref="Command_360_Capture_Tooltip"
|
||||
execute_function="Floater.ToggleOrBringToFront"
|
||||
execute_parameters="360capture"
|
||||
is_running_function="Floater.IsOpen"
|
||||
is_running_parameters="360capture"
|
||||
/>
|
||||
</commands>
|
||||
|
|
|
|||
|
|
@ -2076,6 +2076,28 @@
|
|||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>BrowserFileAccessFromFileUrls</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Allow access to local files via file urls in the embedded browser</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>BrowserPluginsEnabled</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enable Web plugins in the built-in Web browser?</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>ChatBarCustomWidth</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -3539,7 +3561,7 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>DoubleClickTeleport</key>
|
||||
<key>DoubleClickTeleport</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enable double-click to teleport where allowed (afects minimap and people panel)</string>
|
||||
|
|
@ -8858,7 +8880,7 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderHiDPI</key>
|
||||
<key>RenderHiDPI</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enable support for HiDPI displays, like Retina (MacOS X ONLY, requires restart)</string>
|
||||
|
|
@ -11580,7 +11602,7 @@
|
|||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
</map>
|
||||
<key>NearbyListShowMap</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -16632,30 +16654,83 @@
|
|||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>CefVerboseLog</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enable/disable CEF verbose loggingk</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>ResetUIScaleOnFirstRun</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Resets the UI scale factor on first run due to changed display scaling behavior</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>360CaptureUseInterestListCap</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Flag if set, uses the new InterestList cap to ask the simulator for full content</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>360CaptureJPEGEncodeQuality</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Quality value to use in the JPEG encoder (0..100)</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>U32</string>
|
||||
<key>Value</key>
|
||||
<integer>95</integer>
|
||||
</map>
|
||||
<key>360CaptureDebugSaveImage</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Flag if set, saves off each cube map as an image, as well as the JavaScript data URL, for debugging purposes</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>360CaptureOutputImageWidth</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Width of the output 360 equirectangular image</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>U32</string>
|
||||
<key>Value</key>
|
||||
<integer>4096</integer>
|
||||
</map>
|
||||
<key>360CaptureHideAvatars</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Flag if set, removes all the avatars from the 360 snapshot</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>360CaptureCameraFOV</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Field of view of the WebGL camera that converts the cubemap to an equirectangular image</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>U32</string>
|
||||
<key>Value</key>
|
||||
<integer>75</integer>
|
||||
</map>
|
||||
<key>ResetUIScaleOnFirstRun</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Resets the UI scale factor on first run due to changed display scaling behavior</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
</map>
|
||||
</llsd>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,904 @@
|
|||
/**
|
||||
* @file llfloater360capture.cpp
|
||||
* @author Callum Prentice (callum@lindenlab.com)
|
||||
* @brief Floater code for the 360 Capture feature
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2011, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "llfloater360capture.h"
|
||||
|
||||
#include "llagent.h"
|
||||
#include "llagentui.h"
|
||||
#include "llbase64.h"
|
||||
#include "llcallbacklist.h"
|
||||
#include "llenvironment.h"
|
||||
#include "llimagejpeg.h"
|
||||
#include "llmediactrl.h"
|
||||
#include "llradiogroup.h"
|
||||
#include "llslurl.h"
|
||||
#include "lltextbox.h"
|
||||
#include "lltrans.h"
|
||||
#include "lluictrlfactory.h"
|
||||
#include "llversioninfo.h"
|
||||
#include "llviewercamera.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llviewerpartsim.h"
|
||||
#include "llviewerregion.h"
|
||||
#include "llviewerwindow.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
LLFloater360Capture::LLFloater360Capture(const LLSD& key)
|
||||
: LLFloater(key)
|
||||
{
|
||||
// The handle to embedded browser that we use to
|
||||
// render the WebGL preview as we as host the
|
||||
// Cube Map to Equirectangular image code
|
||||
mWebBrowser = nullptr;
|
||||
|
||||
// Ask the simulator to send us everything (and not just
|
||||
// what it thinks the connected Viewer can see) until
|
||||
// such time as we ask it not to (the dtor). If we crash or
|
||||
// otherwise, exit before this is turned off, the Simulator
|
||||
// will take care of cleaning up for us.
|
||||
if (gSavedSettings.getBOOL("360CaptureUseInterestListCap"))
|
||||
{
|
||||
// send everything to us for as long as this floater is open
|
||||
const bool send_everything = true;
|
||||
changeInterestListMode(send_everything);
|
||||
}
|
||||
}
|
||||
|
||||
LLFloater360Capture::~LLFloater360Capture()
|
||||
{
|
||||
if (mWebBrowser)
|
||||
{
|
||||
mWebBrowser->navigateStop();
|
||||
mWebBrowser->clearCache();
|
||||
mWebBrowser->unloadMediaSource();
|
||||
}
|
||||
|
||||
// Tell the Simulator not to send us everything anymore
|
||||
// and revert to the regular "keyhole" frustum of interest
|
||||
// list updates.
|
||||
if (gSavedSettings.getBOOL("360CaptureUseInterestListCap"))
|
||||
{
|
||||
const bool send_everything = false;
|
||||
changeInterestListMode(send_everything);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL LLFloater360Capture::postBuild()
|
||||
{
|
||||
mCaptureBtn = getChild<LLUICtrl>("capture_button");
|
||||
mCaptureBtn->setCommitCallback(boost::bind(&LLFloater360Capture::onCapture360ImagesBtn, this));
|
||||
|
||||
mSaveLocalBtn = getChild<LLUICtrl>("save_local_button");
|
||||
mSaveLocalBtn->setCommitCallback(boost::bind(&LLFloater360Capture::onSaveLocalBtn, this));
|
||||
mSaveLocalBtn->setEnabled(false);
|
||||
|
||||
mWebBrowser = getChild<LLMediaCtrl>("360capture_contents");
|
||||
mWebBrowser->addObserver(this);
|
||||
|
||||
// There is a group of radio buttons that define the quality
|
||||
// by each having a 'value' that is returns equal to the pixel
|
||||
// size (width == height)
|
||||
mQualityRadioGroup = getChild<LLRadioGroup>("360_quality_selection");
|
||||
mQualityRadioGroup->setCommitCallback(boost::bind(&LLFloater360Capture::onChooseQualityRadioGroup, this));
|
||||
|
||||
// UX/UI called for preview mode (always the first index/option)
|
||||
// by default each time vs restoring the last value
|
||||
mQualityRadioGroup->setSelectedIndex(0);
|
||||
|
||||
// Construct a URL pointing to the first page to load. Although
|
||||
// we do not use this page for anything (after some significant
|
||||
// design changes), we retain the code to load the start page
|
||||
// in case that changes again one day. It also makes sure the
|
||||
// embedded browser is active and ready to go for when the real
|
||||
// page with the 360 preview is navigated to.
|
||||
std::string url = STRINGIZE(
|
||||
"file:///" <<
|
||||
getHTMLBaseFolder() <<
|
||||
mDefaultHTML
|
||||
);
|
||||
mWebBrowser->navigateTo(url);
|
||||
|
||||
// initial pass at determining what size (width == height since
|
||||
// the cube map images are square) we should capture at.
|
||||
setSourceImageSize();
|
||||
|
||||
// the size of the output equirectangular image. The height of an EQR image
|
||||
// is always 1/2 of the width so we should not store it but rather,
|
||||
// calculate it from the width directly
|
||||
mOutputImageWidth = gSavedSettings.getU32("360CaptureOutputImageWidth");
|
||||
mOutputImageHeight = mOutputImageWidth / 2;
|
||||
|
||||
// enable resizing and enable for width and for height
|
||||
enableResizeCtrls(true, true, true);
|
||||
|
||||
// initial heading that consumers of the equirectangular image
|
||||
// (such as Facebook or Flickr) use to position initial view -
|
||||
// we set during capture - stored as degrees (0..359)
|
||||
mInitialHeadingDeg = 0.0;
|
||||
|
||||
// save directory in which to store the images (must obliviously be
|
||||
// writable by the viewer). Also create it for users who haven't
|
||||
// used the 360 feature before.
|
||||
mImageSaveDir = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + "eqrimg";
|
||||
LLFile::mkdir(mImageSaveDir);
|
||||
|
||||
// We do an initial capture when the floater is opened, albeit at a 'preview'
|
||||
// quality level (really low resolution, but really fast)
|
||||
onCapture360ImagesBtn();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// called when the user choose a quality level using
|
||||
// the buttons in the radio group
|
||||
void LLFloater360Capture::onChooseQualityRadioGroup()
|
||||
{
|
||||
// set the size of the captured cube map images based
|
||||
// on the quality level chosen
|
||||
setSourceImageSize();
|
||||
}
|
||||
|
||||
// Using a new capability, tell the simulator that we want it to send everything
|
||||
// it knows about and not just what is in front of the camera, in its view
|
||||
// frustum. We need this feature so that the contents of the region that appears
|
||||
// in the 6 snapshots which we cannot see and is normally not "considered", is
|
||||
// also rendered. Typically, this is turned on when the 360 capture floater is
|
||||
// opened and turned off when it is closed.
|
||||
// Note: for this version, we do not have a way to determine when "everything"
|
||||
// has arrived and has been rendered so for now, the proposal is that users
|
||||
// will need to experiment with the low resolution version and wait for some
|
||||
// (hopefully) small period of time while the full contents resolves.
|
||||
// Pass in a flag to ask the simulator/interest list to "send everything" or
|
||||
// not (the default mode)
|
||||
void LLFloater360Capture::changeInterestListMode(bool send_everything)
|
||||
{
|
||||
LLSD body;
|
||||
|
||||
if (send_everything)
|
||||
{
|
||||
body["mode"] = LLSD::String("360");
|
||||
}
|
||||
else
|
||||
{
|
||||
body["mode"] = LLSD::String("default");
|
||||
}
|
||||
|
||||
if (gAgent.requestPostCapability("InterestList", body, [](const LLSD & response)
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"InterestList capability responded: \n" <<
|
||||
ll_pretty_print_sd(response) <<
|
||||
LL_ENDL;
|
||||
}))
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"Successfully posted an InterestList capability request with payload: \n" <<
|
||||
ll_pretty_print_sd(body) <<
|
||||
LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"Unable to post an InterestList capability request with payload: \n" <<
|
||||
ll_pretty_print_sd(body) <<
|
||||
LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
// There is is a setting (360CaptureSourceImageSize) that holds the size
|
||||
// (width == height since it's a square) of each of the 6 source snapshots.
|
||||
// However there are some known (and I dare say, some more unknown conditions
|
||||
// where the specified size is not possible and this function tries to figure it
|
||||
// out and change that setting to the optimal value for the current conditions.
|
||||
void LLFloater360Capture::setSourceImageSize()
|
||||
{
|
||||
mSourceImageSize = mQualityRadioGroup->getSelectedValue().asInteger();
|
||||
|
||||
// If deferred rendering is off, we need to shrink the window we capture
|
||||
// until it's smaller than the Viewer window dimensions.
|
||||
if (!LLPipeline::sRenderDeferred)
|
||||
{
|
||||
LLRect window_rect = gViewerWindow->getWindowRectRaw();
|
||||
S32 window_width = window_rect.getWidth();
|
||||
S32 window_height = window_rect.getHeight();
|
||||
|
||||
while (mSourceImageSize > window_width || mSourceImageSize > window_height)
|
||||
{
|
||||
mSourceImageSize /= 2;
|
||||
LL_INFOS("360Capture") << "Deferred rendering is forcing a smaller capture size: " << mSourceImageSize << LL_ENDL;
|
||||
}
|
||||
|
||||
// there has to be an easier way than this to get the value
|
||||
// from the radio group item at index 0. Why doesn't
|
||||
// LLRadioGroup::getSelectedValue(int index) exist?
|
||||
int index = mQualityRadioGroup->getSelectedIndex();
|
||||
mQualityRadioGroup->setSelectedIndex(0);
|
||||
int min_size = mQualityRadioGroup->getSelectedValue().asInteger();
|
||||
mQualityRadioGroup->setSelectedIndex(index);
|
||||
|
||||
// If the maximum size we can support falls below a threshold then
|
||||
// we should display a message in the log so we can try to debug
|
||||
// why this is happening
|
||||
if (mSourceImageSize < min_size)
|
||||
{
|
||||
LL_INFOS("360Capture") << "Small snapshot size due to deferred rendering and small app window" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function shouldn't exist! We use the tooltip text from
|
||||
// the corresponding XUI file (floater_360capture.xml) as the
|
||||
// overlay text for the final web page to inform the user
|
||||
// about the quality level in play. There ouught to be a
|
||||
// UI function like LLView* getSelectedItemView() or similar
|
||||
// but as far as I can tell, there isn't so we have to resort
|
||||
// to finding it ourselves with this icky code..
|
||||
const std::string LLFloater360Capture::getSelectedQualityTooltip()
|
||||
{
|
||||
// safey (or bravery?)
|
||||
if (mQualityRadioGroup != nullptr)
|
||||
{
|
||||
// for all the child widgets for the radio group
|
||||
// (just the radio buttons themselves I think)
|
||||
for (child_list_const_reverse_iter_t iter = mQualityRadioGroup->getChildList()->rbegin();
|
||||
iter != mQualityRadioGroup->getChildList()->rend();
|
||||
++iter)
|
||||
{
|
||||
// if we match the selected index (which we can get easily)
|
||||
// with our position in the list of children
|
||||
if (mQualityRadioGroup->getSelectedIndex() ==
|
||||
std::distance(mQualityRadioGroup->getChildList()->rend(), iter) - 1)
|
||||
{
|
||||
// return the plain old tooltip text
|
||||
return (*iter)->getToolTip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not found or not available, return an empty string
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Some of the 'magic' happens via a web page in an HTML directory
|
||||
// and this code provides a single point of reference for its' location
|
||||
const std::string LLFloater360Capture::getHTMLBaseFolder()
|
||||
{
|
||||
std::string folder_name = gDirUtilp->getSkinDir();
|
||||
folder_name += gDirUtilp->getDirDelimiter();
|
||||
folder_name += "html";
|
||||
folder_name += gDirUtilp->getDirDelimiter();
|
||||
folder_name += "common";
|
||||
folder_name += gDirUtilp->getDirDelimiter();
|
||||
folder_name += "equirectangular";
|
||||
folder_name += gDirUtilp->getDirDelimiter();
|
||||
|
||||
return folder_name;
|
||||
}
|
||||
|
||||
// triggered when the 'capture' button in the UI is pressed
|
||||
void LLFloater360Capture::onCapture360ImagesBtn()
|
||||
{
|
||||
// launch the main capture code in a coroutine so we can
|
||||
// yield/suspend at some points to give the main UI
|
||||
// thread a look-in occasionally.
|
||||
LLCoros::instance().launch("capture360cap", [this]()
|
||||
{
|
||||
capture360Images();
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the full path name for a given JavaScript file in the HTML folder. We
|
||||
// use this ultimately as a parameter to the main web app so it knows where to find
|
||||
// the JavaScript array containing the 6 cube map images, stored as data URLs
|
||||
const std::string LLFloater360Capture::makeFullPathToJS(const std::string filename)
|
||||
{
|
||||
std::string full_js_path = mImageSaveDir;
|
||||
full_js_path += gDirUtilp->getDirDelimiter();
|
||||
full_js_path += filename;
|
||||
|
||||
return full_js_path;
|
||||
}
|
||||
|
||||
// Write the header/prequel portion of the JavaScript array of data urls
|
||||
// that we use to store the cube map images in (so the web code can load
|
||||
// them without tweaking browser security - we'd have to do this if they
|
||||
// we stored as plain old images) This deliberately overwrites the old
|
||||
// one, if it exists
|
||||
void LLFloater360Capture::writeDataURLHeader(const std::string filename)
|
||||
{
|
||||
std::ofstream file_handle(filename.c_str());
|
||||
if (file_handle.is_open())
|
||||
{
|
||||
file_handle << "// cube map images for Second Life Viewer panorama 360 images" << std::endl;
|
||||
file_handle.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Write the footer/sequel portion of the JavaScript image code. When this is
|
||||
// called, the current file on disk will contain the header and the 6 data
|
||||
// URLs, each with a well specified name. This final piece of JavaScript code
|
||||
// creates an array from those data URLs that the main application can
|
||||
// reference and read.
|
||||
void LLFloater360Capture::writeDataURLFooter(const std::string filename)
|
||||
{
|
||||
std::ofstream file_handle(filename.c_str(), std::ios_base::app);
|
||||
if (file_handle.is_open())
|
||||
{
|
||||
file_handle << "var cubemap_img_js = [" << std::endl;
|
||||
file_handle << " img_posx, img_negx," << std::endl;
|
||||
file_handle << " img_posy, img_negy," << std::endl;
|
||||
file_handle << " img_posz, img_negz," << std::endl;
|
||||
file_handle << "];" << std::endl;
|
||||
|
||||
file_handle.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Given a filename, a chunk of data (representing an image file) and the size
|
||||
// of the buffer, we create a BASE64 encoded string and use it to build a JavaScript
|
||||
// data URL that represents the image in a web browser environment
|
||||
bool LLFloater360Capture::writeDataURL(const std::string filename, const std::string prefix, U8* data, unsigned int data_len)
|
||||
{
|
||||
LL_INFOS("360Capture") << "Writing data URL for " << prefix << " to " << filename << LL_ENDL;
|
||||
|
||||
const std::string data_url = LLBase64::encode(data, data_len);
|
||||
|
||||
std::ofstream file_handle(filename.c_str(), std::ios_base::app);
|
||||
if (file_handle.is_open())
|
||||
{
|
||||
file_handle << "var img_";
|
||||
file_handle << prefix;
|
||||
file_handle << " = '";
|
||||
file_handle << "data:image/jpeg; base64,";
|
||||
file_handle << data_url;
|
||||
file_handle << "'";
|
||||
file_handle << std::endl;
|
||||
file_handle.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encode the image from each of the 6 snapshots and save it out to
|
||||
// the JavaScript array of data URLs
|
||||
void LLFloater360Capture::encodeAndSave(LLPointer<LLImageRaw> raw_image, const std::string filename, const std::string prefix)
|
||||
{
|
||||
// the default quality for the JPEG encoding is set quite high
|
||||
// but this still seems to be a reasonable compromise for
|
||||
// quality/size and is still much smaller than equivalent PNGs
|
||||
int jpeg_encode_quality = gSavedSettings.getU32("360CaptureJPEGEncodeQuality");
|
||||
LLPointer<LLImageJPEG> jpeg_image = new LLImageJPEG(jpeg_encode_quality);
|
||||
|
||||
// Actually encode the JPEG image. This is where a lot of time
|
||||
// is spent now that the snapshot capture process has been
|
||||
// optimized. The encode_time parameter doesn't appear to be
|
||||
// used anymore.
|
||||
const int encode_time = 0;
|
||||
bool resultjpeg = jpeg_image->encode(raw_image, encode_time);
|
||||
|
||||
if (resultjpeg)
|
||||
{
|
||||
// save individual cube map images as real JPEG files
|
||||
// for debugging or curiosity) based on debug settings
|
||||
if (gSavedSettings.getBOOL("360CaptureDebugSaveImage"))
|
||||
{
|
||||
const std::string jpeg_filename = STRINGIZE(
|
||||
gDirUtilp->getLindenUserDir() <<
|
||||
gDirUtilp->getDirDelimiter() <<
|
||||
"eqrimg" <<
|
||||
gDirUtilp->getDirDelimiter() <<
|
||||
prefix <<
|
||||
"." <<
|
||||
jpeg_image->getExtension()
|
||||
);
|
||||
|
||||
LL_INFOS("360Capture") << "Saving debug JPEG image as " << jpeg_filename << LL_ENDL;
|
||||
jpeg_image->save(jpeg_filename);
|
||||
}
|
||||
|
||||
// actually write the JPEG image to disk as a data URL
|
||||
writeDataURL(filename, prefix, jpeg_image->getData(), jpeg_image->getDataSize());
|
||||
}
|
||||
}
|
||||
|
||||
// Defer back to the main loop for a single rendered frame to give
|
||||
// the renderer a chance to update the UI if it is needed
|
||||
void LLFloater360Capture::suspendForAFrame()
|
||||
{
|
||||
const U32 frame_count_delta = 1;
|
||||
U32 curr_frame_count = LLFrameTimer::getFrameCount();
|
||||
while (LLFrameTimer::getFrameCount() <= curr_frame_count + frame_count_delta)
|
||||
{
|
||||
llcoro::suspend();
|
||||
}
|
||||
}
|
||||
|
||||
// A debug version of the snapshot code that simply fills the
|
||||
// buffer with a pattern that can be used to investigate
|
||||
// issues with encoding and saving off each RAW image.
|
||||
// Probably not needed anymore but saving here just in case.
|
||||
void LLFloater360Capture::mockSnapShot(LLImageRaw* raw)
|
||||
{
|
||||
unsigned int width = raw->getWidth();
|
||||
unsigned int height = raw->getHeight();
|
||||
unsigned int depth = raw->getComponents();
|
||||
unsigned char* pixels = raw->getData();
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
unsigned long offset = y * width * depth + x * depth;
|
||||
unsigned char red = x * 256 / width;
|
||||
unsigned char green = y * 256 / height;
|
||||
unsigned char blue = ((x + y) / 2) * 256 / (width + height) / 2;
|
||||
pixels[offset + 0] = red;
|
||||
pixels[offset + 1] = green;
|
||||
pixels[offset + 2] = blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The main code that actually captures all 6 images and then saves them out to
|
||||
// disk before navigating the embedded web browser to the page with the WebGL
|
||||
// application that consumes them and creates an EQR image. This code runs as a
|
||||
// coroutine so it can be suspended at certain points.
|
||||
void LLFloater360Capture::capture360Images()
|
||||
{
|
||||
// recheck the size of the cube map source images in case it changed
|
||||
// since it was set when we opened the floater
|
||||
setSourceImageSize();
|
||||
|
||||
// disable buttons while we are capturing
|
||||
mCaptureBtn->setEnabled(false);
|
||||
mSaveLocalBtn->setEnabled(false);
|
||||
|
||||
// determine whether or not to include avatar in the scene as we capture the 360 panorama
|
||||
if (gSavedSettings.getBOOL("360CaptureHideAvatars"))
|
||||
{
|
||||
// Turn off the avatar if UI tells us to hide it.
|
||||
// Note: the original call to gAvatar.hide(FALSE) did *not* hide
|
||||
// attachments and so for most residents, there would be some debris
|
||||
// left behind in the snapshot.
|
||||
// Note: this toggles so if it set to on, this will turn it off and
|
||||
// the subsequent call to the same thing after capture is finished
|
||||
// will turn it back on again. Similarly, for the case where it
|
||||
// was set to off - I think this is what we need
|
||||
LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR);
|
||||
}
|
||||
|
||||
// these are the 6 directions we will point the camera - essentially,
|
||||
// North, South, East, West, Up, Down
|
||||
LLVector3 look_dirs[6] = { LLVector3(1, 0, 0), LLVector3(0, 1, 0), LLVector3(0, 0, 1), LLVector3(-1, 0, 0), LLVector3(0, -1, 0), LLVector3(0, 0, -1) };
|
||||
LLVector3 look_upvecs[6] = { LLVector3(0, 0, 1), LLVector3(0, 0, 1), LLVector3(0, -1, 0), LLVector3(0, 0, 1), LLVector3(0, 0, 1), LLVector3(0, 1, 0) };
|
||||
|
||||
// save current view/camera settings so we can restore them afterwards
|
||||
S32 old_occlusion = LLPipeline::sUseOcclusion;
|
||||
LLPipeline::sUseOcclusion = 0;
|
||||
LLViewerCamera* camera = LLViewerCamera::getInstance();
|
||||
F32 old_fov = camera->getView();
|
||||
F32 old_aspect = camera->getAspect();
|
||||
F32 old_yaw = camera->getYaw();
|
||||
|
||||
// stop the motion of as much of the world moving as much as we can
|
||||
freezeWorld(true);
|
||||
|
||||
// Save the direction (in degrees) the camera is looking when we
|
||||
// take the shot since that is what we write to image metadata
|
||||
// 'GPano:InitialViewHeadingDegrees' field.
|
||||
// We need to convert from the angle getYaw() gives us into something
|
||||
// the XMP data field wants (N=0, E=90, S=180, W= 270 etc.)
|
||||
mInitialHeadingDeg = (360 + 90 - (int)(camera->getYaw() * RAD_TO_DEG)) % 360;
|
||||
LL_INFOS("360Capture") << "Recording a heading of " << (int)(mInitialHeadingDeg) << LL_ENDL;
|
||||
|
||||
// camera constants for the square, cube map capture image
|
||||
camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV
|
||||
camera->setView(F_PI_BY_TWO);
|
||||
camera->yaw(0.0);
|
||||
|
||||
// record how many times we changed camera to try to understand the "all shots are the same issue"
|
||||
unsigned int camera_changed_times = 0;
|
||||
|
||||
// the name of the JavaScript file written out that contains the 6 cube map images
|
||||
// stored as a JavaScript array of data URLs. If you change this filename, you must
|
||||
// also change the corresponding entry in the HTML file that uses it -
|
||||
// (newview/skins/default/html/common/equirectangular/display_eqr.html)
|
||||
const std::string cumemap_js_filename("cubemap_img.js");
|
||||
|
||||
// construct the full path to this file - typically stored in the users'
|
||||
// Second Life settings / username / eqrimg folder.
|
||||
const std::string cubemap_js_full_path = makeFullPathToJS(cumemap_js_filename);
|
||||
|
||||
// Write the JavaScript file header (the top of the file before the
|
||||
// declarations of the actual data URLs array). In practice, all this writes
|
||||
// is a comment - it's main purpose is to reset the file from the last time
|
||||
// it was written
|
||||
writeDataURLHeader(cubemap_js_full_path);
|
||||
|
||||
// the names of the prefixes we assign as the name to each data URL and are then
|
||||
// consumed by the WebGL application. Nominally, they stand for positive and
|
||||
// negative in the X/Y/Z directions.
|
||||
static const std::string prefixes[6] =
|
||||
{
|
||||
"posx", "posz", "posy",
|
||||
"negx", "negz", "negy",
|
||||
};
|
||||
|
||||
// time the encode process for later optimization
|
||||
auto encode_time_total = 0.0;
|
||||
|
||||
// for each of the 6 directions we shoot...
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
// these buffers are where the raw, captured pixels are stored and
|
||||
// the first time we use them, we have to make a new one
|
||||
if (mRawImages[i] == nullptr)
|
||||
{
|
||||
mRawImages[i] = new LLImageRaw(mSourceImageSize, mSourceImageSize, 3);
|
||||
}
|
||||
else
|
||||
// subsequent capture with floater open so we resize the buffer from
|
||||
// the previous run
|
||||
{
|
||||
// LLImageRaw deletes the old one via operator= but just to be
|
||||
// sure, we delete its' large data member first...
|
||||
mRawImages[i]->deleteData();
|
||||
mRawImages[i] = new LLImageRaw(mSourceImageSize, mSourceImageSize, 3);
|
||||
}
|
||||
|
||||
// set up camera to look in each direction
|
||||
camera->lookDir(look_dirs[i], look_upvecs[i]);
|
||||
|
||||
// record if camera changed to try to understand the "all shots are the same issue"
|
||||
if (camera->isChanged())
|
||||
{
|
||||
++camera_changed_times;
|
||||
}
|
||||
|
||||
// call the (very) simplified snapshot code that simply deals
|
||||
// with a single image, no sub-images etc. but is very fast
|
||||
gViewerWindow->simpleSnapshot(mRawImages[i], mSourceImageSize, mSourceImageSize);
|
||||
|
||||
// encode each image and write to disk while saving how long it took to do so
|
||||
auto t_start = std::chrono::high_resolution_clock::now();
|
||||
encodeAndSave(mRawImages[i], cubemap_js_full_path, prefixes[i]);
|
||||
auto t_end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(t_end - t_start);
|
||||
encode_time_total += duration.count();
|
||||
|
||||
// ping the main loop in case the snapshot process takes a really long
|
||||
// time and we get disconnected
|
||||
LLAppViewer::instance()->pingMainloopTimeout("LLFloater360Capture::capture360Images");
|
||||
}
|
||||
|
||||
// display time to encode all 6 images. It tends to be a fairly linear
|
||||
// time for each so we don't need to worry about displaying the time
|
||||
// for each - this gives us plenty to use for optimizing
|
||||
LL_INFOS("360Capture") <<
|
||||
"Time to encode and save 6 images was " <<
|
||||
encode_time_total <<
|
||||
" seconds" <<
|
||||
LL_ENDL;
|
||||
|
||||
// Write the JavaScript file footer (the bottom of the file after the
|
||||
// declarations of the actual data URLs array). The footer comprises of
|
||||
// a JavaScript array declaration that references the 6 data URLs generated
|
||||
// previously and is what is referred to in the display HTML file
|
||||
// (newview/skins/default/html/common/equirectangular/display_eqr.html)
|
||||
writeDataURLFooter(cubemap_js_full_path);
|
||||
|
||||
// unfreeze the world now we have our shots
|
||||
freezeWorld(false);
|
||||
|
||||
// restore original view/camera/avatar settings settings
|
||||
camera->setAspect(old_aspect);
|
||||
camera->setView(old_fov);
|
||||
camera->yaw(old_yaw);
|
||||
LLPipeline::sUseOcclusion = old_occlusion;
|
||||
|
||||
// if we toggled off the avatar because the Hide check box was ticked,
|
||||
// we should toggle it back to where it was before we started the capture
|
||||
if (gSavedSettings.getBOOL("360CaptureHideAvatars"))
|
||||
{
|
||||
LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR);
|
||||
}
|
||||
|
||||
// record that we missed some shots in the log for later debugging
|
||||
// note: we use 5 and not 6 because the first shot isn't regarded
|
||||
// as a change - only the subsequent 5 are
|
||||
if (camera_changed_times < 5)
|
||||
{
|
||||
LL_INFOS("360Capture") << "Warning: we only captured " << camera_changed_times << " images." << LL_ENDL;
|
||||
}
|
||||
|
||||
// now we have the 6 shots saved in a well specified location,
|
||||
// we can load the web content that uses them
|
||||
std::string url = "file:///" + getHTMLBaseFolder() + mEqrGenHTML;
|
||||
mWebBrowser->navigateTo(url);
|
||||
|
||||
// allow the UI to update by suspending and waiting for the
|
||||
// main render loop to update the UI
|
||||
suspendForAFrame();
|
||||
|
||||
// page is loaded and ready so we can turn on the buttons again
|
||||
mCaptureBtn->setEnabled(true);
|
||||
mSaveLocalBtn->setEnabled(true);
|
||||
}
|
||||
|
||||
// once the request is made to navigate to the web page containing the code
|
||||
// to process the 6 images into an EQR one, we have to wait for it to finish
|
||||
// loaded - we get a "navigate complete" event when that happens that we can act on
|
||||
void LLFloater360Capture::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
// not used right now but retaining because this event might
|
||||
// be useful for a feature I am hoping to add
|
||||
case MEDIA_EVENT_LOCATION_CHANGED:
|
||||
break;
|
||||
|
||||
// navigation in the browser completed
|
||||
case MEDIA_EVENT_NAVIGATE_COMPLETE:
|
||||
{
|
||||
// Confirm that the navigation event does indeed apply to the
|
||||
// page we are looking for. At the moment, this is the only
|
||||
// one we care about so the test is superfluous but that might change.
|
||||
std::string navigate_url = self->getNavigateURI();
|
||||
if (navigate_url.find(mEqrGenHTML) != std::string::npos)
|
||||
{
|
||||
// this string is being passed across to the web so replace all the windows backslash
|
||||
// characters with forward slashes or (I think) the backslashes are treated as escapes
|
||||
std::replace(mImageSaveDir.begin(), mImageSaveDir.end(), '\\', '/');
|
||||
|
||||
// we store the camera FOV (field of view) in a saved setting since this feels
|
||||
// like something it would be interesting to change and experiment with
|
||||
int camera_fov = gSavedSettings.getU32("360CaptureCameraFOV");
|
||||
|
||||
// compose the overlay for the final web page that tells the user
|
||||
// what level of quality the capture was taken with
|
||||
std::string overlay_label = "'" + getSelectedQualityTooltip() + "'";
|
||||
|
||||
// so now our page is loaded and images are in place - call
|
||||
// the JavaScript init script with some parameters to initialize
|
||||
// the WebGL based preview
|
||||
const std::string cmd = STRINGIZE(
|
||||
"init("
|
||||
<< mOutputImageWidth
|
||||
<< ", "
|
||||
<< mOutputImageHeight
|
||||
<< ", "
|
||||
<< "'"
|
||||
<< mImageSaveDir
|
||||
<< "'"
|
||||
<< ", "
|
||||
<< camera_fov
|
||||
<< ", "
|
||||
<< LLViewerCamera::getInstance()->getYaw()
|
||||
<< ", "
|
||||
<< overlay_label
|
||||
<< ")"
|
||||
);
|
||||
|
||||
// execute the command on the page
|
||||
mWebBrowser->getMediaPlugin()->executeJavaScript(cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// called when the user wants to save the cube maps off to the final EQR image
|
||||
void LLFloater360Capture::onSaveLocalBtn()
|
||||
{
|
||||
// region name and URL
|
||||
std::string region_name; // no sensible default
|
||||
std::string region_url("http://secondlife.com");
|
||||
LLViewerRegion* region = gAgent.getRegion();
|
||||
if (region)
|
||||
{
|
||||
// region names can (and do) contain characters that would make passing
|
||||
// them into a JavaScript function problematic - single quotes for example
|
||||
// so we must escape/encode both
|
||||
region_name = region->getName();
|
||||
|
||||
// escaping/encoding is a minefield - let's just remove any offending characters from the region name
|
||||
region_name.erase(std::remove(region_name.begin(), region_name.end(), '\''), region_name.end());
|
||||
region_name.erase(std::remove(region_name.begin(), region_name.end(), '\"'), region_name.end());
|
||||
|
||||
// fortunately there is already an escaping function built into the SLURL generation code
|
||||
LLSLURL slurl;
|
||||
bool is_escaped = true;
|
||||
LLAgentUI::buildSLURL(slurl, is_escaped);
|
||||
region_url = slurl.getSLURLString();
|
||||
}
|
||||
|
||||
// build suggested filename (the one that appears as the default
|
||||
// in the Save dialog box)
|
||||
const std::string suggested_filename = generate_proposed_filename();
|
||||
|
||||
// This string (the name of the product plus a truncated version number (no build))
|
||||
// is used in the XMP block as the name of the generating and stitching software.
|
||||
// We save the version number here and not in the more generic 'software' item
|
||||
// because that might help us determine something about the image in the future.
|
||||
const std::string client_version = STRINGIZE(
|
||||
LLVersionInfo::instance().getChannel() <<
|
||||
" " <<
|
||||
LLVersionInfo::instance().getShortVersion()
|
||||
);
|
||||
|
||||
// save the time the image was created. I don't know if this should be
|
||||
// UTC/ZULU or the users' local time. It probably doesn't matter.
|
||||
std::time_t result = std::time(nullptr);
|
||||
std::string ctime_str = std::ctime(&result);
|
||||
std::string time_str = ctime_str.substr(0, ctime_str.length() - 1);
|
||||
|
||||
// build the JavaScript data structure that is used to pass all the
|
||||
// variables into the JavaScript function on the web page loaded into
|
||||
// the embedded browser component of the floater.
|
||||
const std::string xmp_details = STRINGIZE(
|
||||
"{ " <<
|
||||
"pano_version: '" << "2.2.1" << "', " <<
|
||||
"software: '" << LLVersionInfo::instance().getChannel() << "', " <<
|
||||
"capture_software: '" << client_version << "', " <<
|
||||
"stitching_software: '" << client_version << "', " <<
|
||||
"width: " << mOutputImageWidth << ", " <<
|
||||
"height: " << mOutputImageHeight << ", " <<
|
||||
"heading: " << mInitialHeadingDeg << ", " <<
|
||||
"actual_source_image_size: " << mQualityRadioGroup->getSelectedValue().asInteger() << ", " <<
|
||||
"scaled_source_image_size: " << mSourceImageSize << ", " <<
|
||||
"first_photo_date: '" << time_str << "', " <<
|
||||
"last_photo_date: '" << time_str << "', " <<
|
||||
"region_name: '" << region_name << "', " <<
|
||||
"region_url: '" << region_url << "', " <<
|
||||
" }"
|
||||
);
|
||||
|
||||
// build the JavaScript command to send to the web browser
|
||||
const std::string cmd = "saveAsEqrImage(\"" + suggested_filename + "\", " + xmp_details + ")";
|
||||
|
||||
// send it to the browser instance, triggering the equirectangular capture
|
||||
// process and complimentary offer to save the image
|
||||
mWebBrowser->getMediaPlugin()->executeJavaScript(cmd);
|
||||
}
|
||||
|
||||
// We capture all 6 images sequentially and if parts of the world are moving
|
||||
// E.G. clouds, water, objects - then we may get seams or discontinuities
|
||||
// when the images are combined to form the EQR image. This code tries to
|
||||
// stop everything so we can shoot for seamless shots. There is probably more
|
||||
// we can do here - e.g. waves in the water probably won't line up.
|
||||
void LLFloater360Capture::freezeWorld(bool enable)
|
||||
{
|
||||
static bool clouds_scroll_paused = false;
|
||||
if (enable)
|
||||
{
|
||||
// record the cloud scroll current value so we can restore it
|
||||
clouds_scroll_paused = LLEnvironment::instance().isCloudScrollPaused();
|
||||
|
||||
// stop the clouds moving
|
||||
LLEnvironment::instance().pauseCloudScroll();
|
||||
|
||||
// freeze all avatars
|
||||
LLCharacter* avatarp;
|
||||
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
||||
iter != LLCharacter::sInstances.end(); ++iter)
|
||||
{
|
||||
avatarp = *iter;
|
||||
mAvatarPauseHandles.push_back(avatarp->requestPause());
|
||||
}
|
||||
|
||||
// freeze everything else
|
||||
gSavedSettings.setBOOL("FreezeTime", true);
|
||||
|
||||
// disable particle system
|
||||
LLViewerPartSim::getInstance()->enable(false);
|
||||
}
|
||||
else // turning off freeze world mode, either temporarily or not.
|
||||
{
|
||||
// restart the clouds moving if they were not paused before
|
||||
// we starting using the 360 capture floater
|
||||
if (clouds_scroll_paused == false)
|
||||
{
|
||||
LLEnvironment::instance().resumeCloudScroll();
|
||||
}
|
||||
|
||||
// thaw all avatars
|
||||
mAvatarPauseHandles.clear();
|
||||
|
||||
// thaw everything else
|
||||
gSavedSettings.setBOOL("FreezeTime", false);
|
||||
|
||||
//enable particle system
|
||||
LLViewerPartSim::getInstance()->enable(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the default filename that appears in the Save dialog box. We try
|
||||
// to encode some metadata about too (region name, EQR dimensions, capture
|
||||
// time) but the user if free to replace this with anything else before
|
||||
// the images is saved.
|
||||
const std::string LLFloater360Capture::generate_proposed_filename()
|
||||
{
|
||||
std::ostringstream filename("");
|
||||
|
||||
// base name
|
||||
filename << "sl360_";
|
||||
|
||||
LLViewerRegion* region = gAgent.getRegion();
|
||||
if (region)
|
||||
{
|
||||
// this looks complex but it's straightforward - removes all non-alpha chars from a string
|
||||
// which in this case is the SL region name - we use it as a proposed filename but the user is free to change
|
||||
std::string region_name = region->getName();
|
||||
std::replace_if(region_name.begin(), region_name.end(), std::not1(std::ptr_fun(isalnum)), '_');
|
||||
if (region_name.length() > 0)
|
||||
{
|
||||
filename << region_name;
|
||||
filename << "_";
|
||||
}
|
||||
}
|
||||
|
||||
// add in resolution to make it easier to tell what you captured later
|
||||
filename << mOutputImageWidth;
|
||||
filename << "x";
|
||||
filename << mOutputImageHeight;
|
||||
filename << "_";
|
||||
|
||||
// Add in the size of the source image (width == height since it was square)
|
||||
// Might be useful later for quality comparisons
|
||||
filename << mSourceImageSize;
|
||||
filename << "_";
|
||||
|
||||
// add in the current HH-MM-SS (with leading 0's) so users can easily save many shots in same folder
|
||||
std::time_t cur_epoch = std::time(nullptr);
|
||||
std::tm* tm_time = std::localtime(&cur_epoch);
|
||||
filename << std::setfill('0') << std::setw(4) << (tm_time->tm_year + 1900);
|
||||
filename << std::setfill('0') << std::setw(2) << (tm_time->tm_mon + 1);
|
||||
filename << std::setfill('0') << std::setw(2) << tm_time->tm_mday;
|
||||
filename << "_";
|
||||
filename << std::setfill('0') << std::setw(2) << tm_time->tm_hour;
|
||||
filename << std::setfill('0') << std::setw(2) << tm_time->tm_min;
|
||||
filename << std::setfill('0') << std::setw(2) << tm_time->tm_sec;
|
||||
|
||||
// the unusual way we save the output image (originates in the
|
||||
// embedded browser and not the C++ code) means that the system
|
||||
// appends ".jpeg" to the file automatically on macOS at least,
|
||||
// so we only need to do it ourselves for windows.
|
||||
#if LL_WINDOWS
|
||||
filename << ".jpg";
|
||||
#endif
|
||||
|
||||
return filename.str();
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* @file llfloater360capture.h
|
||||
* @author Callum Prentice (callum@lindenlab.com)
|
||||
* @brief Floater for the 360 capture feature
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2011, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LL_FLOATER_360CAPTURE_H
|
||||
#define LL_FLOATER_360CAPTURE_H
|
||||
|
||||
#include "llfloater.h"
|
||||
#include "llmediactrl.h"
|
||||
#include "llcharacter.h"
|
||||
|
||||
class LLImageRaw;
|
||||
class LLTextBox;
|
||||
class LLRadioGroup;
|
||||
|
||||
class LLFloater360Capture:
|
||||
public LLFloater,
|
||||
public LLViewerMediaObserver
|
||||
{
|
||||
friend class LLFloaterReg;
|
||||
|
||||
private:
|
||||
LLFloater360Capture(const LLSD& key);
|
||||
|
||||
~LLFloater360Capture();
|
||||
BOOL postBuild() override;
|
||||
void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override;
|
||||
|
||||
void changeInterestListMode(bool send_everything);
|
||||
|
||||
const std::string getHTMLBaseFolder();
|
||||
void capture360Images();
|
||||
|
||||
const std::string makeFullPathToJS(const std::string filename);
|
||||
void writeDataURLHeader(const std::string filename);
|
||||
void writeDataURLFooter(const std::string filename);
|
||||
bool writeDataURL(const std::string filename, const std::string prefix, U8* data, unsigned int data_len);
|
||||
void encodeAndSave(LLPointer<LLImageRaw> raw_image, const std::string filename, const std::string prefix);
|
||||
|
||||
std::vector<LLAnimPauseRequest> mAvatarPauseHandles;
|
||||
void freezeWorld(bool enable);
|
||||
|
||||
void mockSnapShot(LLImageRaw* raw);
|
||||
|
||||
void suspendForAFrame();
|
||||
|
||||
const std::string generate_proposed_filename();
|
||||
|
||||
void setSourceImageSize();
|
||||
|
||||
LLMediaCtrl* mWebBrowser;
|
||||
const std::string mDefaultHTML = "default.html";
|
||||
const std::string mEqrGenHTML = "eqr_gen.html";
|
||||
|
||||
LLUICtrl* mCaptureBtn;
|
||||
void onCapture360ImagesBtn();
|
||||
|
||||
void onSaveLocalBtn();
|
||||
LLUICtrl* mSaveLocalBtn;
|
||||
|
||||
LLRadioGroup* mQualityRadioGroup;
|
||||
void onChooseQualityRadioGroup();
|
||||
const std::string getSelectedQualityTooltip();
|
||||
|
||||
int mSourceImageSize;
|
||||
float mInitialHeadingDeg;
|
||||
int mOutputImageWidth;
|
||||
int mOutputImageHeight;
|
||||
std::string mImageSaveDir;
|
||||
|
||||
LLPointer<LLImageRaw> mRawImages[6];
|
||||
};
|
||||
|
||||
#endif // LL_FLOATER_360CAPTURE_H
|
||||
|
|
@ -179,6 +179,10 @@ void LLFloaterSnapshotBase::ImplBase::updateLayout(LLFloaterSnapshotBase* floate
|
|||
thumbnail_placeholder->reshape(panel_width, thumbnail_placeholder->getRect().getHeight());
|
||||
floaterp->getChild<LLUICtrl>("image_res_text")->setVisible(mAdvanced);
|
||||
floaterp->getChild<LLUICtrl>("file_size_label")->setVisible(mAdvanced);
|
||||
if (floaterp->hasChild("360_label", TRUE))
|
||||
{
|
||||
floaterp->getChild<LLUICtrl>("360_label")->setVisible(mAdvanced);
|
||||
}
|
||||
if(!floaterp->isMinimized())
|
||||
{
|
||||
floaterp->reshape(floater_width, floaterp->getRect().getHeight());
|
||||
|
|
@ -992,6 +996,10 @@ BOOL LLFloaterSnapshot::postBuild()
|
|||
getChild<LLButton>("retract_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this));
|
||||
getChild<LLButton>("extend_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this));
|
||||
|
||||
getChild<LLTextBox>("360_label")->setSoundFlags(LLView::MOUSE_UP);
|
||||
getChild<LLTextBox>("360_label")->setShowCursorHand(false);
|
||||
getChild<LLTextBox>("360_label")->setClickedCallback(boost::bind(&LLFloaterSnapshot::on360Snapshot, this));
|
||||
|
||||
// Filters
|
||||
LLComboBox* filterbox = getChild<LLComboBox>("filters_combobox");
|
||||
std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList();
|
||||
|
|
@ -1118,6 +1126,12 @@ void LLFloaterSnapshot::onExtendFloater()
|
|||
impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot"));
|
||||
}
|
||||
|
||||
void LLFloaterSnapshot::on360Snapshot()
|
||||
{
|
||||
LLFloaterReg::showInstance("360capture");
|
||||
closeFloater();
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLFloaterSnapshotBase::onClose(bool app_quitting)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ public:
|
|||
static void update();
|
||||
|
||||
void onExtendFloater();
|
||||
void on360Snapshot();
|
||||
|
||||
static LLFloaterSnapshot* getInstance();
|
||||
static LLFloaterSnapshot* findInstance();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "llcommandhandler.h"
|
||||
#include "llcompilequeue.h"
|
||||
#include "llfasttimerview.h"
|
||||
#include "llfloater360capture.h"
|
||||
#include "llfloaterabout.h"
|
||||
#include "llfloateraddpaymentmethod.h"
|
||||
#include "llfloaterauction.h"
|
||||
|
|
@ -195,6 +196,7 @@ void LLViewerFloaterReg::registerFloaters()
|
|||
// *NOTE: Please keep these alphabetized for easier merges
|
||||
|
||||
LLFloaterAboutUtil::registerFloater();
|
||||
LLFloaterReg::add("360capture", "floater_360capture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloater360Capture>);
|
||||
LLFloaterReg::add("block_timers", "floater_fast_timers.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFastTimerView>);
|
||||
LLFloaterReg::add("about_land", "floater_about_land.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLand>);
|
||||
LLFloaterReg::add("add_payment_method", "floater_add_payment_method.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAddPaymentMethod>);
|
||||
|
|
|
|||
|
|
@ -1746,8 +1746,16 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_
|
|||
media_source->cookies_enabled( cookies_enabled || clean_browser);
|
||||
|
||||
// collect 'javascript enabled' setting from prefs and send to embedded browser
|
||||
bool javascript_enabled = gSavedSettings.getBOOL( "BrowserJavascriptEnabled" );
|
||||
media_source->setJavascriptEnabled( javascript_enabled || clean_browser);
|
||||
bool javascript_enabled = gSavedSettings.getBOOL("BrowserJavascriptEnabled");
|
||||
media_source->setJavascriptEnabled(javascript_enabled || clean_browser);
|
||||
|
||||
// collect 'web security disabled' (see Chrome --web-security-disabled) setting from prefs and send to embedded browser
|
||||
bool web_security_disabled = gSavedSettings.getBOOL("BrowserWebSecurityDisabled");
|
||||
media_source->setWebSecurityDisabled(web_security_disabled || clean_browser);
|
||||
|
||||
// collect setting indicates if local file access from file URLs is allowed from prefs and send to embedded browser
|
||||
bool file_access_from_file_urls = gSavedSettings.getBOOL("BrowserFileAccessFromFileUrls");
|
||||
media_source->setFileAccessFromFileUrlsEnabled(file_access_from_file_urls || clean_browser);
|
||||
|
||||
// As of SL-15559 PDF files do not load in CEF v91 we enable plugins
|
||||
// but explicitly disable Flash (PDF support in CEF is now treated as a plugin)
|
||||
|
|
@ -1907,6 +1915,15 @@ void LLViewerMediaImpl::loadURI()
|
|||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
void LLViewerMediaImpl::executeJavaScript(const std::string& code)
|
||||
{
|
||||
if (mMediaSource)
|
||||
{
|
||||
mMediaSource->executeJavaScript(code);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
void LLViewerMediaImpl::setSize(int width, int height)
|
||||
{
|
||||
|
|
@ -3212,8 +3229,19 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla
|
|||
|
||||
case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD:
|
||||
{
|
||||
//llinfos << "Media event - file download requested - filename is " << self->getFileDownloadFilename() << llendl;
|
||||
LLNotificationsUtil::add("MediaFileDownloadUnsupported");
|
||||
LL_DEBUGS("Media") << "Media event - file download requested - filename is " << plugin->getFileDownloadFilename() << LL_ENDL;
|
||||
// pick a file from SAVE FILE dialog
|
||||
|
||||
// need a better algorithm that this or else, pass in type of save type
|
||||
// from event that initiated it - this is okay for now - only thing
|
||||
// that saves is 360s
|
||||
std::string suggested_filename = plugin->getFileDownloadFilename();
|
||||
LLFilePicker::ESaveFilter filter = LLFilePicker::FFSAVE_ALL;
|
||||
if (suggested_filename.find(".jpg") != std::string::npos || suggested_filename.find(".jpeg") != std::string::npos)
|
||||
filter = LLFilePicker::FFSAVE_JPEG;
|
||||
if (suggested_filename.find(".png") != std::string::npos)
|
||||
filter = LLFilePicker::FFSAVE_PNG;
|
||||
init_threaded_picker_save_dialog(plugin, filter, suggested_filename);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ public:
|
|||
bool initializeMedia(const std::string& mime_type);
|
||||
bool initializePlugin(const std::string& media_type);
|
||||
void loadURI();
|
||||
void executeJavaScript(const std::string& code);
|
||||
LLPluginClassMedia* getMediaPlugin() { return mMediaSource.get(); }
|
||||
void setSize(int width, int height);
|
||||
|
||||
|
|
|
|||
|
|
@ -1263,12 +1263,55 @@ class LLAdvancedDumpScriptedCamera : public view_listener_t
|
|||
class LLAdvancedDumpRegionObjectCache : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
{
|
||||
handle_dump_region_object_cache(NULL);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class LLAdvancedInterestListFullUpdate : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
LLSD request;
|
||||
LLSD body;
|
||||
static bool using_360 = false;
|
||||
|
||||
if (using_360)
|
||||
{
|
||||
body["mode"] = LLSD::String("default");
|
||||
}
|
||||
else
|
||||
{
|
||||
body["mode"] = LLSD::String("360");
|
||||
}
|
||||
using_360 = !using_360;
|
||||
|
||||
if (gAgent.requestPostCapability("InterestList", body, [](const LLSD& response)
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"InterestList capability responded: \n" <<
|
||||
ll_pretty_print_sd(response) <<
|
||||
LL_ENDL;
|
||||
}))
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"Successfully posted an InterestList capability request with payload: \n" <<
|
||||
ll_pretty_print_sd(body) <<
|
||||
LL_ENDL;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("360Capture") <<
|
||||
"Unable to post an InterestList capability request with payload: \n" <<
|
||||
ll_pretty_print_sd(body) <<
|
||||
LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LLAdvancedBuyCurrencyTest : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
|
|
@ -2309,11 +2352,10 @@ class LLAdvancedLeaveAdminStatus : public view_listener_t
|
|||
// Advanced > Debugging //
|
||||
//////////////////////////
|
||||
|
||||
|
||||
class LLAdvancedForceErrorBreakpoint : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
{
|
||||
force_error_breakpoint(NULL);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -9189,6 +9231,7 @@ void initialize_menus()
|
|||
// Advanced > World
|
||||
view_listener_t::addMenu(new LLAdvancedDumpScriptedCamera(), "Advanced.DumpScriptedCamera");
|
||||
view_listener_t::addMenu(new LLAdvancedDumpRegionObjectCache(), "Advanced.DumpRegionObjectCache");
|
||||
view_listener_t::addMenu(new LLAdvancedInterestListFullUpdate(), "Advanced.InterestListFullUpdate");
|
||||
|
||||
// Advanced > UI
|
||||
commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser
|
||||
|
|
|
|||
|
|
@ -3745,6 +3745,7 @@ void process_kill_object(LLMessageSystem *mesgsys, void **user_data)
|
|||
{
|
||||
LLColor4 color(0.f,1.f,0.f,1.f);
|
||||
gPipeline.addDebugBlip(objectp->getPositionAgent(), color);
|
||||
LL_DEBUGS("MessageBlip") << "Kill blip for local " << local_id << " at " << objectp->getPositionAgent() << LL_ENDL;
|
||||
}
|
||||
|
||||
// Do the kill
|
||||
|
|
|
|||
|
|
@ -2402,6 +2402,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
|
|||
color.setVec(1.f, 0.f, 0.f, 1.f);
|
||||
}
|
||||
gPipeline.addDebugBlip(getPositionAgent(), color);
|
||||
LL_DEBUGS("MessageBlip") << "Update type " << (S32)update_type << " blip for local " << mLocalID << " at " << getPositionAgent() << LL_ENDL;
|
||||
}
|
||||
|
||||
const F32 MAG_CUTOFF = F_APPROXIMATELY_ZERO;
|
||||
|
|
|
|||
|
|
@ -2956,6 +2956,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
|
|||
capabilityNames.append("IncrementCOFVersion");
|
||||
AISAPI::getCapNames(capabilityNames);
|
||||
|
||||
capabilityNames.append("InterestList");
|
||||
|
||||
capabilityNames.append("GetDisplayNames");
|
||||
capabilityNames.append("GetExperiences");
|
||||
capabilityNames.append("AgentExperiences");
|
||||
|
|
|
|||
|
|
@ -5160,6 +5160,83 @@ BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei
|
|||
return ret;
|
||||
}
|
||||
|
||||
BOOL LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height)
|
||||
{
|
||||
gDisplaySwapBuffers = FALSE;
|
||||
|
||||
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
setCursor(UI_CURSOR_WAIT);
|
||||
|
||||
BOOL prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI) ? TRUE : FALSE;
|
||||
if (prev_draw_ui != false)
|
||||
{
|
||||
LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI);
|
||||
}
|
||||
|
||||
LLPipeline::sShowHUDAttachments = FALSE;
|
||||
LLRect window_rect = getWorldViewRectRaw();
|
||||
|
||||
S32 original_width = LLPipeline::sRenderDeferred ? gPipeline.mDeferredScreen.getWidth() : gViewerWindow->getWorldViewWidthRaw();
|
||||
S32 original_height = LLPipeline::sRenderDeferred ? gPipeline.mDeferredScreen.getHeight() : gViewerWindow->getWorldViewHeightRaw();
|
||||
|
||||
LLRenderTarget scratch_space;
|
||||
U32 color_fmt = GL_RGBA;
|
||||
const bool use_depth_buffer = true;
|
||||
const bool use_stencil_buffer = true;
|
||||
if (scratch_space.allocate(image_width, image_height, color_fmt, use_depth_buffer, use_stencil_buffer))
|
||||
{
|
||||
if (gPipeline.allocateScreenBuffer(image_width, image_height))
|
||||
{
|
||||
mWorldViewRectRaw.set(0, image_height, image_width, 0);
|
||||
|
||||
scratch_space.bindTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
scratch_space.release();
|
||||
gPipeline.allocateScreenBuffer(original_width, original_height);
|
||||
}
|
||||
}
|
||||
|
||||
const U32 subfield = 0;
|
||||
const bool do_rebuild = true;
|
||||
const F32 zoom = 1.0;
|
||||
const bool for_snapshot = TRUE;
|
||||
display(do_rebuild, zoom, subfield, for_snapshot);
|
||||
|
||||
glReadPixels(
|
||||
0, 0,
|
||||
image_width,
|
||||
image_height,
|
||||
GL_RGB, GL_UNSIGNED_BYTE,
|
||||
raw->getData()
|
||||
);
|
||||
stop_glerror();
|
||||
|
||||
gDisplaySwapBuffers = FALSE;
|
||||
gDepthDirty = TRUE;
|
||||
|
||||
if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI))
|
||||
{
|
||||
if (prev_draw_ui != false)
|
||||
{
|
||||
LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI);
|
||||
}
|
||||
}
|
||||
|
||||
LLPipeline::sShowHUDAttachments = TRUE;
|
||||
|
||||
setCursor(UI_CURSOR_ARROW);
|
||||
|
||||
gPipeline.resetDrawOrders();
|
||||
mWorldViewRectRaw = window_rect;
|
||||
scratch_space.flush();
|
||||
scratch_space.release();
|
||||
gPipeline.allocateScreenBuffer(original_width, original_height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLViewerWindow::destroyWindow()
|
||||
{
|
||||
if (mWindow)
|
||||
|
|
|
|||
|
|
@ -357,7 +357,10 @@ public:
|
|||
BOOL saveSnapshot(const std::string& filename, S32 image_width, S32 image_height, BOOL show_ui = TRUE, BOOL show_hud = TRUE, BOOL do_rebuild = FALSE, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, LLSnapshotModel::ESnapshotFormat format = LLSnapshotModel::SNAPSHOT_FORMAT_BMP);
|
||||
BOOL rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, BOOL keep_window_aspect = TRUE, BOOL is_texture = FALSE,
|
||||
BOOL show_ui = TRUE, BOOL show_hud = TRUE, BOOL do_rebuild = FALSE, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, S32 max_size = MAX_SNAPSHOT_IMAGE_SIZE);
|
||||
BOOL thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, BOOL show_ui, BOOL show_hud, BOOL do_rebuild, LLSnapshotModel::ESnapshotLayerType type);
|
||||
|
||||
BOOL simpleSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height);
|
||||
|
||||
BOOL thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, BOOL show_ui, BOOL show_hud, BOOL do_rebuild, LLSnapshotModel::ESnapshotLayerType type);
|
||||
BOOL isSnapshotLocSet() const;
|
||||
void resetSnapshotLoc() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color:#000;
|
||||
background-image: linear-gradient(white 2px, transparent 2px),
|
||||
linear-gradient(90deg, white 2px, transparent 2px),
|
||||
linear-gradient(rgba(255,255,255,.3) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,.3) 1px, transparent 1px);
|
||||
background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
|
||||
background-position:-2px -2px, -2px -2px, -1px -1px, -1px -1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function start() {
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', start);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
background: #333;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#error_message {
|
||||
z-index: 2;
|
||||
background-color: #aa3333;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
pointer-events:none;
|
||||
font-family: monospace;
|
||||
font-size: 3em;
|
||||
color: white;
|
||||
border-radius: 1em;
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%)
|
||||
}
|
||||
#quality_window {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
width: auto;
|
||||
border-radius: 16px;
|
||||
height: auto;
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
background-color: rgba(200,200,200,0.35);
|
||||
color: #000;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="js/three.min.js"></script>
|
||||
<script src="js/OrbitControls.js"></script>
|
||||
<script src="js/jpeg_encoder_basic.js" type="text/javascript"></script>
|
||||
<script src="js/CubemapToEquirectangular.js"></script>
|
||||
<script>
|
||||
var controls, camera, scene, renderer, equiManaged;
|
||||
|
||||
function init(eqr_width, eqr_height, img_path, camera_fov, initial_heading, overlay_label) {
|
||||
|
||||
camera = new THREE.PerspectiveCamera(camera_fov, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.x = 0.01;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
|
||||
renderer = new THREE.WebGLRenderer();
|
||||
renderer.autoClear = false;
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
|
||||
var cubemap_img_js_url = img_path + '/cubemap_img.js';
|
||||
var cubemap_image_js = document.createElement('script');
|
||||
cubemap_image_js.setAttribute('type', 'text/javascript');
|
||||
cubemap_image_js.setAttribute('src', cubemap_img_js_url);
|
||||
document.getElementsByTagName('head')[0].appendChild(cubemap_image_js);
|
||||
cubemap_image_js.onload = function () {
|
||||
document.getElementById("error_message").style.display = 'none'
|
||||
scene.background = new THREE.CubeTextureLoader().load(cubemap_img_js);
|
||||
equiManaged = new CubemapToEquirectangular(renderer, true, eqr_width, eqr_height);
|
||||
};
|
||||
cubemap_image_js.onerror = function () {
|
||||
document.getElementById("error_message").style.display = 'inline-block'
|
||||
};
|
||||
|
||||
document.body.appendChild(renderer.domElement);
|
||||
window.addEventListener('resize', onWindowResize, false);
|
||||
|
||||
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = 0.2;
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.15;
|
||||
controls.rotateSpeed = -0.5;
|
||||
|
||||
// initial direction the camera faces
|
||||
// We cannot edit camera rotation directly as the OrbitControls will
|
||||
// immediately reset it so we need some math to tell the controls
|
||||
// there to look at initially. Note there is also an offset of π/2 since
|
||||
// the Viewer and three.js have slightly different coordinate systems
|
||||
var spherical_target = new THREE.Spherical(1, Math.PI / 2, initial_heading + Math.PI / 2)
|
||||
var target = new THREE.Vector3().setFromSpherical(spherical_target)
|
||||
camera.position.set(target.x, target.y, target.z);
|
||||
controls.update();
|
||||
controls.saveState();
|
||||
|
||||
// update the text that gets passed in from the C++ app for
|
||||
// the translucent overlay label that tells us what we are seeing
|
||||
document.getElementById('quality_window').innerHTML = overlay_label;
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
'mousedown',
|
||||
function (event) {
|
||||
controls.autoRotate = false;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
window.addEventListener(
|
||||
'dblclick',
|
||||
function (event) {
|
||||
controls.autoRotate = true;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
function saveAsEqrImage(filename, xmp_details) {
|
||||
equiManaged.update(camera, scene, filename, xmp_details);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
</script>
|
||||
<div id="error_message">UNABLE TO LOAD EQR IMAGE</div>
|
||||
<div id="quality_window">Preview Quality</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -131,7 +131,8 @@ with the same filename but different name
|
|||
<texture name="Check_Mark" file_name="icons/check_mark.png" preload="true" />
|
||||
|
||||
<texture name="Checker" file_name="checker.png" preload="false" />
|
||||
|
||||
|
||||
<texture name="Command_360_Capture_Icon" file_name="toolbar_icons/360_capture.png" preload="true" />
|
||||
<texture name="Command_AboutLand_Icon" file_name="toolbar_icons/land.png" preload="true" />
|
||||
<texture name="Command_Appearance_Icon" file_name="toolbar_icons/appearance.png" preload="true" />
|
||||
<texture name="Command_Avatar_Icon" file_name="toolbar_icons/avatars.png" preload="true" />
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 793 B |
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<floater can_resize="true"
|
||||
height="400"
|
||||
layout="topleft"
|
||||
min_height="300"
|
||||
min_width="400"
|
||||
name="360capture"
|
||||
help_topic="360capture"
|
||||
save_rect="true"
|
||||
title="360 Capture"
|
||||
width="800">
|
||||
<panel layout="topleft"
|
||||
background_visible="true"
|
||||
top="0"
|
||||
follows="left|bottom|top"
|
||||
left="0"
|
||||
width="200"
|
||||
bg_opaque_color="0.195 0.195 0.195 1"
|
||||
background_opaque="true"
|
||||
height="400"
|
||||
name="ui_panel">
|
||||
<text
|
||||
follows="top|left|right"
|
||||
height="16"
|
||||
layout="topleft"
|
||||
left="10"
|
||||
top="10"
|
||||
width="100">
|
||||
Quality level
|
||||
</text>
|
||||
<radio_group
|
||||
control_name="360QualitySelection"
|
||||
follows="left|top"
|
||||
height="94"
|
||||
layout="topleft"
|
||||
left_delta="20"
|
||||
name="360_quality_selection"
|
||||
top_pad="0"
|
||||
width="180">
|
||||
<radio_item
|
||||
height="20"
|
||||
label="Preview (fast)"
|
||||
layout="topleft"
|
||||
left="0"
|
||||
name="preview_quality"
|
||||
value="128"
|
||||
tool_tip="Preview quality"
|
||||
top="0"
|
||||
width="100" />
|
||||
<radio_item
|
||||
height="20"
|
||||
label="Medium"
|
||||
layout="topleft"
|
||||
left_delta="0"
|
||||
name="medium_quality"
|
||||
value="512"
|
||||
tool_tip="Medium quality"
|
||||
top_delta="20"
|
||||
width="100" />
|
||||
<radio_item
|
||||
height="20"
|
||||
label="High"
|
||||
layout="topleft"
|
||||
left_delta="0"
|
||||
name="high_quality"
|
||||
value="1024"
|
||||
tool_tip="High quality"
|
||||
top_delta="20"
|
||||
width="100" />
|
||||
<radio_item
|
||||
height="20"
|
||||
label="Maximum"
|
||||
layout="topleft"
|
||||
left_delta="0"
|
||||
name="maximum_quality"
|
||||
value="2048"
|
||||
tool_tip="Maximum quality"
|
||||
top_delta="20"
|
||||
width="100" />
|
||||
</radio_group>
|
||||
<check_box control_name="360CaptureHideAvatars"
|
||||
follows="left|top"
|
||||
height="15"
|
||||
label="Hide all avatars"
|
||||
layout="left"
|
||||
left="10"
|
||||
name="360_hide_avatar"
|
||||
top_delta="0"
|
||||
width="100"/>
|
||||
<button follows="left|top"
|
||||
height="20"
|
||||
label="Capture 360 image"
|
||||
layout="topleft"
|
||||
left="10"
|
||||
name="capture_button"
|
||||
top_delta="32"
|
||||
width="180" />
|
||||
<button follows="left|top"
|
||||
height="20"
|
||||
label="Save as.."
|
||||
layout="topleft"
|
||||
left="10"
|
||||
name="save_local_button"
|
||||
top_delta="35"
|
||||
width="180" />
|
||||
</panel>
|
||||
<web_browser top="0"
|
||||
follows="all"
|
||||
bg_opaque_color="0.225 0.225 0.225 1"
|
||||
left="200"
|
||||
width="600"
|
||||
height="380"
|
||||
name="360capture_contents"
|
||||
trusted_content="true" />
|
||||
<text follows="bottom"
|
||||
layout="topleft"
|
||||
name="statusbar"
|
||||
height="17"
|
||||
left="210"
|
||||
top="383"
|
||||
width="600">
|
||||
Click and drag on the image to pan
|
||||
</text>
|
||||
</floater>
|
||||
|
|
@ -400,7 +400,7 @@
|
|||
layout="topleft"
|
||||
name="img_info_border"
|
||||
top_pad="0"
|
||||
right="-10"
|
||||
right="-130"
|
||||
follows="left|top|right"
|
||||
left_delta="0"/>
|
||||
<text
|
||||
|
|
@ -411,11 +411,10 @@
|
|||
height="14"
|
||||
layout="topleft"
|
||||
left="220"
|
||||
right="-20"
|
||||
halign="left"
|
||||
name="image_res_text"
|
||||
top_delta="5"
|
||||
width="200">
|
||||
width="250">
|
||||
[WIDTH]px (width) x [HEIGHT]px (height)
|
||||
</text>
|
||||
<text
|
||||
|
|
@ -423,7 +422,7 @@
|
|||
font="SansSerifSmall"
|
||||
height="14"
|
||||
layout="topleft"
|
||||
left="-65"
|
||||
left="-185"
|
||||
length="1"
|
||||
halign="right"
|
||||
name="file_size_label"
|
||||
|
|
@ -432,4 +431,19 @@
|
|||
width="50">
|
||||
[SIZE] KB
|
||||
</text>
|
||||
<text
|
||||
follows="right|top"
|
||||
font="SansSerifSmall"
|
||||
height="14"
|
||||
layout="topleft"
|
||||
left="-130"
|
||||
length="1"
|
||||
halign="right"
|
||||
name="360_label"
|
||||
text_color="0.3 0.82 1 1"
|
||||
top_delta="0"
|
||||
type="string"
|
||||
width="115">
|
||||
Take a 360 snapshot
|
||||
</text>
|
||||
</floater>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
can_dock="false"
|
||||
can_minimize="false"
|
||||
can_resize="false"
|
||||
height="375"
|
||||
height="430"
|
||||
help_topic="toybox"
|
||||
layout="topleft"
|
||||
legacy_header_height="18"
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
Buttons will appear as shown or as icon-only depending on each toolbar's settings.
|
||||
</text>
|
||||
<toolbar
|
||||
bottom="310"
|
||||
bottom="365"
|
||||
button_display_mode="icons_with_text"
|
||||
follows="all"
|
||||
left="20"
|
||||
|
|
@ -81,11 +81,11 @@
|
|||
<panel
|
||||
bevel_style="none"
|
||||
border="true"
|
||||
bottom="311"
|
||||
bottom="366"
|
||||
follows="left|bottom|right"
|
||||
left="20"
|
||||
right="-20"
|
||||
top="311" />
|
||||
top="366" />
|
||||
<button
|
||||
follows="left|bottom|right"
|
||||
height="23"
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
layout="topleft"
|
||||
left="185"
|
||||
name="btn_clear_all"
|
||||
top="330"
|
||||
top="385"
|
||||
width="130">
|
||||
<button.commit_callback function="Toybox.ClearAll" />
|
||||
</button>
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
layout="topleft"
|
||||
left="335"
|
||||
name="btn_restore_defaults"
|
||||
top="330"
|
||||
top="385"
|
||||
width="130">
|
||||
<button.commit_callback function="Toybox.RestoreDefaults" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -733,6 +733,15 @@
|
|||
function="Floater.Show"
|
||||
parameter="snapshot" />
|
||||
</menu_item_call>
|
||||
|
||||
<menu_item_call
|
||||
label="Capture 360"
|
||||
name="Capture 360"
|
||||
shortcut="control|alt|shift|s">
|
||||
<menu_item_call.on_click
|
||||
function="Floater.Show"
|
||||
parameter="360capture" />
|
||||
</menu_item_call>
|
||||
<menu_item_separator/>
|
||||
<menu_item_call
|
||||
label="Place profile"
|
||||
|
|
@ -3427,6 +3436,14 @@ function="World.EnvPreset"
|
|||
<menu_item_call.on_click
|
||||
function="Advanced.DumpRegionObjectCache" />
|
||||
</menu_item_call>
|
||||
|
||||
<menu_item_call
|
||||
label="Interest List: Full Update"
|
||||
name="Interest List: Full Update"
|
||||
shortcut="alt|shift|I">
|
||||
<menu_item_call.on_click
|
||||
function="Advanced.InterestListFullUpdate" />
|
||||
</menu_item_call>
|
||||
</menu>
|
||||
<menu
|
||||
create_jump_keys="true"
|
||||
|
|
|
|||
|
|
@ -4124,6 +4124,8 @@ Try enclosing path to the editor with double quotes.
|
|||
|
||||
<!-- commands -->
|
||||
|
||||
<string
|
||||
name="Command_360_Capture_Label">360 Capture</string>
|
||||
<string name="Command_AboutLand_Label">About land</string>
|
||||
<string name="Command_Appearance_Label">Outfits</string>
|
||||
<string name="Command_Avatar_Label">Complete avatars</string>
|
||||
|
|
@ -4154,6 +4156,8 @@ Try enclosing path to the editor with double quotes.
|
|||
<string name="Command_View_Label">Camera controls</string>
|
||||
<string name="Command_Voice_Label">Voice settings</string>
|
||||
|
||||
<string
|
||||
name="Command_360_Capture_Tooltip">Capture a 360 panorama image</string>
|
||||
<string name="Command_AboutLand_Tooltip">Information about the land you're visiting</string>
|
||||
<string name="Command_Appearance_Tooltip">Change your avatar</string>
|
||||
<string name="Command_Avatar_Tooltip">Choose a complete avatar</string>
|
||||
|
|
|
|||
|
|
@ -158,18 +158,12 @@ class ViewerManifest(LLManifest):
|
|||
self.path("*/xui/*/widgets/*.xml")
|
||||
self.path("*/*.xml")
|
||||
|
||||
# Local HTML files (e.g. loading screen)
|
||||
# The claim is that we never use local html files any
|
||||
# longer. But rather than commenting out this block, let's
|
||||
# rename every html subdirectory as html.old. That way, if
|
||||
# we're wrong, a user actually does have the relevant
|
||||
# files; s/he just needs to rename every html.old
|
||||
# directory back to html to recover them.
|
||||
with self.prefix(src="*/html", dst="*/html.old"):
|
||||
self.path("*.png")
|
||||
self.path("*/*/*.html")
|
||||
self.path("*/*/*.gif")
|
||||
|
||||
# Update: 2017-11-01 CP Now we store app code in the html folder
|
||||
# Initially the HTML/JS code to render equirectangular
|
||||
# images for the 360 capture feature but more to follow.
|
||||
with self.prefix(src="*/html", dst="*/html"):
|
||||
self.path("*/*/*/*.js")
|
||||
self.path("*/*/*.html")
|
||||
|
||||
#build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems
|
||||
#platform is computed above with other arg parsing
|
||||
|
|
|
|||
Loading…
Reference in New Issue