diff --git a/cmake/modules/FindSDL2.cmake b/cmake/modules/FindSDL2.cmake new file mode 100644 index 0000000000..f24af555a2 --- /dev/null +++ b/cmake/modules/FindSDL2.cmake @@ -0,0 +1,249 @@ +# Locate SDL2 library +# This module defines +# SDL2_LIBRARY, the name of the library to link against +# SDL2_FOUND, if false, do not try to link to SDL2 +# SDL2_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL2_BUILDING_LIBRARY +# If this is defined, then no SDL2_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL2_LIBRARY variable. +# +# Don't forget to include SDL2main.h and SDL2main.m your project for the +# OS X framework based version. (Other versions link to -lSDL2main which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration +# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library +# (SDL2.dll, libsdl2.so, SDL2.framework, etc). +# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. +# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL2_LIBRARY +# variable, but when these values are unset, SDL2_LIBRARY does not get created. +# +# +# $SDL2 is an environment variable that would +# correspond to the ./configure --prefix=$SDL2 +# used in building SDL2. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL2 guidelines. +# Added a search for SDL2main which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL2_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL2/SDL.h to just SDL.h +# This needed to change because "proper" SDL2 convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL2/ (see FreeBSD). +# +# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake +# module with the minor edit of changing "SDL" to "SDL2" where necessary. This +# was not created for redistribution, and exists temporarily pending official +# SDL2 CMake modules. +# +# Note that on windows this will only search for the 32bit libraries, to search +# for 64bit change x86/i686-w64 to x64/x86_64-w64 + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# CMake - Cross Platform Makefile Generator +# Copyright 2000-2014 Kitware, Inc. +# Copyright 2000-2011 Insight Software Consortium +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PATH(SDL2_INCLUDE_DIR SDL.h + HINTS + $ENV{SDL2} + PATH_SUFFIXES include/SDL2 include SDL2 + i686-w64-mingw32/include/SDL2 + x86_64-w64-mingw32/include/SDL2 + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include/SDL2 + /usr/include/SDL2 + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +# Lookup the 64 bit libs on x64 +IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + FIND_LIBRARY(SDL2_LIBRARY_TEMP SDL2 + HINTS + $ENV{SDL2} + PATH_SUFFIXES lib64 lib + lib/x64 + x86_64-w64-mingw32/lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) +# On 32bit build find the 32bit libs +ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) + FIND_LIBRARY(SDL2_LIBRARY_TEMP SDL2 + HINTS + $ENV{SDL2} + PATH_SUFFIXES lib + lib/x86 + i686-w64-mingw32/lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) +ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) + +IF(NOT SDL2_BUILDING_LIBRARY) + IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL2main for compatibility even though they don't + # necessarily need it. + # Lookup the 64 bit libs on x64 + IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + FIND_LIBRARY(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + $ENV{SDL2} + PATH_SUFFIXES lib64 lib + lib/x64 + x86_64-w64-mingw32/lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + # On 32bit build find the 32bit libs + ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) + FIND_LIBRARY(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + $ENV{SDL2} + PATH_SUFFIXES lib + lib/x86 + i686-w64-mingw32/lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) + ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL2_BUILDING_LIBRARY) + +# SDL2 may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +SET(SDL2_FOUND "NO") + IF(SDL2_LIBRARY_TEMP) + # For SDL2main + IF(NOT SDL2_BUILDING_LIBRARY) + IF(SDL2MAIN_LIBRARY) + SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(SDL2MAIN_LIBRARY) + ENDIF(NOT SDL2_BUILDING_LIBRARY) + + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") + + SET(SDL2_FOUND "YES") +ENDIF(SDL2_LIBRARY_TEMP) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 30d843c7eb..ee2c9ae796 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -59,19 +59,6 @@ "type": "password", "help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.", "value-hidden": true - }, - { - "name": "allowed_users", - "type": "table", - "label": "Allowed Users", - "help": "A list of usernames for the High Fidelity users you want to allow into your domain. Users not found in this list will not be allowed to connect.", - "numbered": false, - "can_add": true, - "can_delete": true, - "key": { - "name": "username", - "label": "Username" - } } ] }, @@ -86,8 +73,6 @@ "label": "Zones", "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, - "can_add": true, - "can_delete": true, "key": { "name": "name", "label": "Name", @@ -114,6 +99,33 @@ } ] }, + { + "name": "attenuation_coefficients", + "type": "table", + "label": "Attenuation Coefficients", + "help": "In this table you can set custom attenuation coefficients between audio zones", + "numbered": false, + "columns": [ + { + "name": "source", + "label": "Source", + "can_set": true, + "placeholder": "Zone_A" + }, + { + "name": "listener", + "label": "Listener", + "can_set": true, + "placeholder": "Zone_B" + }, + { + "name": "coefficient", + "label": "Attenuation coefficient", + "can_set": true, + "placeholder": "0.18" + } + ] + }, { "name": "enable_filter", "type": "checkbox", diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 4c365ed110..ad889274d4 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -82,4 +82,10 @@ td.buttons { td.buttons .glyphicon { display: block; text-align: center; + font-size: 12px; +} + +tr.new-row { + color: #3c763d; + background-color: #dff0d8; } diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index c2b2babbb2..9d586c8436 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -1,12 +1,22 @@ var Settings = { - showAdvanced: false + showAdvanced: false, + ADVANCED_CLASS: 'advanced-setting', + TRIGGER_CHANGE_CLASS: 'trigger-change', + DATA_ROW_CLASS: 'value-row', + DATA_COL_CLASS: 'value-col', + ADD_ROW_BUTTON_CLASS: 'add-row', + ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', + DEL_ROW_BUTTON_CLASS: 'del-row', + DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', + TABLE_BUTTONS_CLASS: 'buttons', + NEW_ROW_CLASS: 'new-row' }; var viewHelpers = { getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) { setting_name = groupName + "." + setting.name - form_group = "
" + form_group = "
" if (_.has(values, groupName) && _.has(values[groupName], setting.name)) { setting_value = values[groupName][setting.name] @@ -21,12 +31,14 @@ var viewHelpers = { label_class += ' locked' } + common_attrs = " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + + " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + setting_name + "' " + if (setting.type === 'checkbox') { form_group += "" form_group += "
" form_group += ""; form_group += "
" } else if (setting.type === 'table') { @@ -46,15 +58,15 @@ var viewHelpers = { form_group += "" - form_group += "" + form_group += "" } else { if (input_type == 'integer') { input_type = "text" } - form_group += "" } @@ -87,90 +99,33 @@ $(document).ready(function(){ $(window).resize(resizeFn); }) - $('#settings-form').on('click', '.add-row', function(){ - var row = $(this).parents("tr") - var data = row.parent().children(".row-data") - - // Check key spaces - var name = row.children(".key").children("input").val() - if (name.indexOf(' ') !== -1) { - showErrorMessage("Error", "Key contains spaces") - return - } - // Check keys with the same name - var equals = false; - _.each(data.children(".key"), function(element) { - if ($(element).text() === name) { - equals = true - return - } - }) - if (equals) { - showErrorMessage("Error", "Two keys cannot be identical") - return - } - - // Check empty fields - var empty = false; - _.each(row.children(".row-data").children("input"), function(element) { - if ($(element).val().length === 0) { - empty = true - return - } - }) - if (empty) { - showErrorMessage("Error", "Empty field(s)") - return - } - - var input_clone = row.clone() - // Change input row to data row - var full_name = row.parents("table").attr("name") + "." + name - row.attr("class", "row-data") - - _.each(row.children(), function(element) { - if ($(element).hasClass("number")) { // Index row - var numbers = data.children(".number") - if (numbers.length > 0) { - $(element).html(parseInt(numbers.last().text()) + 1) - } else { - $(element).html(1) - } - } else if ($(element).hasClass("buttons")) { // Change buttons - var prevSpan = $(element).parent().prev().children(".buttons").children("span") - var span = $(element).children("span") - if (prevSpan.hasClass("del-row")) { - span.removeClass("glyphicon-ok add-row") - span.addClass("glyphicon-remove del-row") - } else { - span.remove() - } - } else if ($(element).hasClass("key")) { - var input = $(element).children("input") - $(element).html(input.val()) - input.remove() - } else if($(element).hasClass("row-data")) { // Hide inputs - var input = $(element).children("input") - input.attr("type", "hidden") - input.attr("name", full_name + "." + $(element).attr("name")) - input.attr("value", input.val()) - input.attr("data-changed", "true") - - $(element).html($(element).html() + input.val()) - } else { - console.log("Unknown table element") - } - }) - row.parent().append(input_clone) + $('#settings-form').on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ + addTableRow(this); }) - $('#settings-form').on('click', '.del-row', function(){ - var row = $(this).parents("tr") - row.empty() - row.html(""); + $('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ + deleteTableRow(this); }) + + $('#settings-form').on('keypress', 'table input', function(e){ + if (e.keyCode == 13) { + // capture enter in table input + // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click + sibling = $(this).parent('td').next(); + + if (sibling.hasClass(Settings.DATA_COL_CLASS)) { + // set focus to next input + sibling.find('input').focus() + } else if (sibling.hasClass(Settings.TABLE_BUTTONS_CLASS)) { + sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click() + + // set focus to the first input in the new row + $(this).closest('table').find('tr.inputs input:first').focus() + } + } + }); - $('#settings-form').on('change', 'input', function(){ + $('#settings-form').on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true) @@ -179,7 +134,7 @@ $(document).ready(function(){ $('#advanced-toggle-button').click(function(){ Settings.showAdvanced = !Settings.showAdvanced - var advancedSelector = $('.advanced-setting') + var advancedSelector = $('.' + Settings.ADVANCED_CLASS) if (Settings.showAdvanced) { advancedSelector.show() @@ -197,7 +152,6 @@ $(document).ready(function(){ }) $('#settings-form').on('change', 'select', function(){ - console.log("Changed" + $(this)) $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() }) @@ -279,53 +233,70 @@ $('body').on('click', '.save-button', function(e){ }); function makeTable(setting, setting_name, setting_value) { + var isArray = !_.has(setting, 'key') + var html = "" html += "" + setting.help + "" - html += "" + html += "
" // Column names html += "" - if (setting.number === true) { + + if (setting.numbered === true) { html += "" // Row number } - html += "" // Key + + if (setting.key) { + html += "" // Key + } + _.each(setting.columns, function(col) { html += "" // Data }) - if (setting.can_delete === true || setting.can_add === true) { - html += "" // Buttons - } - html += "" + + html += "" - // Rows + // populate rows in the table from existing values var row_num = 1 - _.each(setting_value, function(row, name) { - html += "" + + _.each(setting_value, function(row, indexOrName) { + html += "" + if (setting.numbered === true) { html += "" } - html += "" + + if (setting.key) { + html += "" + } + _.each(setting.columns, function(col) { - html += "" }) - if (setting.can_delete === true) { - html += "" - } else if (setting.can_add === true) { - html += "" - } + + html += "" html += "" + row_num++ }) - // Inputs - if (setting.can_add === true) { - html += makeTableInputs(setting) - } - + // populate inputs in the table for new values + html += makeTableInputs(setting) html += "
#" + setting.key.label + "" + setting.key.label + "" + col.label + "+/-
+/-
" + row_num + "" + name + "" + indexOrName + "" - if (row.hasOwnProperty(col.name)) { - html += row[col.name] + html += "" + + if (isArray) { + colIsArray = _.isArray(row) + colValue = colIsArray ? row : row[col.name] + html += colValue + + // for arrays we add a hidden input to this td so that values can be posted appropriately + html += "" + } else if (row.hasOwnProperty(col.name)) { + html += row[col.name] } + html += "
" return html; @@ -333,35 +304,43 @@ function makeTable(setting, setting_name, setting_value) { function makeTableInputs(setting) { var html = "" + if (setting.numbered === true) { html += "" } - html += "\ - \ - " + + if (setting.key) { + html += "\ + \ + " + } + _.each(setting.columns, function(col) { - html += "\ - \ + html += "\ + \ " }) - html += "" + + html += "" html += "" return html } -function badgeSidebarForDifferences(changedInput) { +function badgeSidebarForDifferences(changedElement) { // figure out which group this input is in - var panelParentID = changedInput.closest('.panel').attr('id') + var panelParentID = changedElement.closest('.panel').attr('id') // get a JSON representation of that section - var rootJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); - var panelJSON = rootJSON[panelParentID] + var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID] + var initialPanelJSON = Settings.initialValues[panelParentID] var badgeValue = 0 + // badge for any settings we have that are not the same or are not present in initialValues for (var setting in panelJSON) { - if (panelJSON[setting] != Settings.initialValues[panelParentID][setting]) { + if (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) + && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting))) { badgeValue += 1 } } @@ -374,6 +353,180 @@ function badgeSidebarForDifferences(changedInput) { $("a[href='#" + panelParentID + "'] .badge").html(badgeValue); } +function addTableRow(add_glyphicon) { + var row = $(add_glyphicon).closest('tr') + + var table = row.parents('table') + var isArray = table.data('setting-type') === 'array' + + var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS) + + if (!isArray) { + // Check key spaces + var key = row.children(".key").children("input").val() + if (key.indexOf(' ') !== -1) { + showErrorMessage("Error", "Key contains spaces") + return + } + // Check keys with the same name + var equals = false; + _.each(columns.children(".key"), function(element) { + if ($(element).text() === key) { + equals = true + return + } + }) + if (equals) { + showErrorMessage("Error", "Two keys cannot be identical") + return + } + } + + // Check empty fields + var empty = false; + _.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) { + if ($(element).val().length === 0) { + empty = true + return + } + }) + + if (empty) { + showErrorMessage("Error", "Empty field(s)") + return + } + + var input_clone = row.clone() + + // Change input row to data row + var table = row.parents("table") + var setting_name = table.attr("name") + var full_name = setting_name + "." + key + row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS) + row.removeClass("inputs") + + _.each(row.children(), function(element) { + if ($(element).hasClass("numbered")) { + // Index row + var numbers = columns.children(".numbered") + if (numbers.length > 0) { + $(element).html(parseInt(numbers.last().text()) + 1) + } else { + $(element).html(1) + } + } else if ($(element).hasClass("buttons")) { + // Change buttons + var span = $(element).children("span") + span.removeClass(Settings.ADD_ROW_SPAN_CLASSES) + span.addClass(Settings.DEL_ROW_SPAN_CLASSES) + } else if ($(element).hasClass("key")) { + var input = $(element).children("input") + $(element).html(input.val()) + input.remove() + } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { + // Hide inputs + var input = $(element).children("input") + input.attr("type", "hidden") + + if (isArray) { + var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length + var key = $(element).attr('name') + + // are there multiple columns or just one? + // with multiple we have an array of Objects, with one we have an array of whatever the value type is + var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length + input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + } else { + input.attr("name", full_name + "." + $(element).attr("name")) + } + + input.attr("data-changed", "true") + + $(element).append(input.val()) + } else { + console.log("Unknown table element") + } + }) + + input_clone.find('input').each(function(){ + $(this).val('') + }); + + if (isArray) { + updateDataChangedForSiblingRows(row, true) + + // the addition of any table row should remove the empty-array-row + row.siblings('.empty-array-row').remove() + } + + badgeSidebarForDifferences($(table)) + + row.parent().append(input_clone) +} + +function deleteTableRow(delete_glyphicon) { + var row = $(delete_glyphicon).closest('tr') + + var table = $(row).closest('table') + var isArray = table.data('setting-type') === 'array' + + if (!isArray) { + // this is a hash row, so we empty it but leave the hidden input blank so it is cleared when we save + row.empty() + row.html(""); + } else { + if (table.find('.' + Settings.DATA_ROW_CLASS).length) { + updateDataChangedForSiblingRows(row) + + // this isn't the last row - we can just remove it + row.remove() + } else { + // this is the last row, we can't remove it completely since we need to post an empty array + row.empty() + + row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS) + row.addClass('empty-array-row') + + row.html(""); + } + } + + // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated + badgeSidebarForDifferences($(table)) +} + +function updateDataChangedForSiblingRows(row, forceTrue) { + // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true + // unless it matches the inital set of values + + if (!forceTrue) { + // figure out which group this row is in + var panelParentID = row.closest('.panel').attr('id') + // get the short name for the setting from the table + var tableShortName = row.closest('table').data('short-name') + + // get a JSON representation of that section + var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] + var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] + + // if they are equal, we don't need data-changed + isTrue = _.isEqual(panelSettingJSON, initialPanelSettingJSON) + } else { + isTrue = true + } + + row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ + var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') + if (isTrue) { + hiddenInput.attr('data-changed', isTrue) + } else { + hiddenInput.removeAttr('data-changed') + } + }) +} + function showRestartModal() { $('#restart-modal').modal({ backdrop: 'static', diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 6a63168579..e7a9eef1ee 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -289,6 +289,9 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV // we've cleared all of the settings below this value, so remove this one too settingMap.remove(key); } + } else if (newValue.isArray()) { + // we just assume array is replacement + settingMap[key] = newValue.toArray().toVariantList(); } } diff --git a/examples/editModels.js b/examples/editModels.js index 2741bdccdd..0a908595a3 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1312,7 +1312,6 @@ var toolBar = (function () { if (clickedOverlay === loadFileMenuItem) { toggleNewModelButton(false); - // TODO BUG: this is bug, if the user has never uploaded a model, this will throw an JS exception file = Window.browse("Select your model file ...", Settings.getValue("LastModelUploadLocation").path(), "Model files (*.fst *.fbx)"); diff --git a/examples/gamepad.js b/examples/gamepad.js new file mode 100644 index 0000000000..b4c2758edd --- /dev/null +++ b/examples/gamepad.js @@ -0,0 +1,249 @@ +// +// controller.js +// examples +// +// Created by Ryan Huffman on 10/9/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var gamepads = {}; + +// Function -> button/axis mappings +var AXIS_STRAFE = Joysticks.AXIS_LEFT_X; +var AXIS_FORWARD = Joysticks.AXIS_LEFT_Y; +var AXIS_ROTATE = Joysticks.AXIS_RIGHT_X; + +var BUTTON_TURN_AROUND = Joysticks.BUTTON_RIGHT_STICK; + +var BUTTON_FLY_UP = Joysticks.BUTTON_RIGHT_SHOULDER; +var BUTTON_FLY_DOWN = Joysticks.BUTTON_LEFT_SHOULDER; +var BUTTON_WARP = Joysticks.BUTTON_FACE_BOTTOM; + +var BUTTON_WARP_FORWARD = Joysticks.BUTTON_DPAD_UP; +var BUTTON_WARP_BACKWARD = Joysticks.BUTTON_DPAD_DOWN; +var BUTTON_WARP_LEFT = Joysticks.BUTTON_DPAD_LEFT; +var BUTTON_WARP_RIGHT = Joysticks.BUTTON_DPAD_RIGHT; + +// Distance in meters to warp via BUTTON_WARP_* +var WARP_DISTANCE = 1; + +// Walk speed in m/s +var MOVE_SPEED = 2; + +// Amount to rotate in radians +var ROTATE_INCREMENT = Math.PI / 8; + +// Pick from above where we want to warp +var WARP_PICK_OFFSET = { x: 0, y: 10, z: 0 }; + +// When warping, the warp position will snap to a target below the current warp position. +// This is the max distance it will snap to. +var WARP_PICK_MAX_DISTANCE = 100; + +var flyDownButtonState = false; +var flyUpButtonState = false; + +// Current move direction, axis aligned - that is, looking down and moving forward +// will not move you into the ground, but instead will keep you on the horizontal plane. +var moveDirection = { x: 0, y: 0, z: 0 }; + +var warpActive = false; +var warpPosition = { x: 0, y: 0, z: 0 }; + +var WARP_SPHERE_SIZE = 1; +var warpSphere = Overlays.addOverlay("sphere", { + position: { x: 0, y: 0, z: 0 }, + size: WARP_SPHERE_SIZE, + color: { red: 0, green: 255, blue: 0 }, + alpha: 1.0, + solid: true, + visible: false, +}); + +var WARP_LINE_HEIGHT = 10; +var warpLine = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z:0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 255, blue: 255}, + alpha: 1, + lineWidth: 5, + visible: false, +}); + +function copyVec3(vec) { + return { x: vec.x, y: vec.y, z: vec.z }; +} + +function activateWarp() { + if (warpActive) return; + warpActive = true; + + updateWarp(); +} + +function updateWarp() { + if (!warpActive) return; + + var look = Quat.getFront(Camera.getOrientation()); + var pitch = Math.asin(look.y); + + // Get relative to looking straight down + pitch += Math.PI / 2; + + // Scale up + pitch *= 2; + var distance = pitch * pitch * pitch; + + var warpDirection = Vec3.normalize({ x: look.x, y: 0, z: look.z }); + warpPosition = Vec3.multiply(warpDirection, distance); + warpPosition = Vec3.sum(MyAvatar.position, warpPosition); + + var pickRay = { + origin: Vec3.sum(warpPosition, WARP_PICK_OFFSET), + direction: { x: 0, y: -1, z: 0 } + }; + + var intersection = Voxels.findRayIntersection(pickRay); + + if (intersection.intersects && intersection.distance < WARP_PICK_MAX_DISTANCE) { + // Warp 1 meter above the object - this is an approximation + // TODO Get the actual offset to the Avatar's feet and plant them to + // the object. + warpPosition = Vec3.sum(intersection.intersection, { x: 0, y: 1, z:0 }); + } + + // Adjust overlays to match warp position + Overlays.editOverlay(warpSphere, { + position: warpPosition, + visible: true, + }); + Overlays.editOverlay(warpLine, { + position: warpPosition, + end: Vec3.sum(warpPosition, { x: 0, y: WARP_LINE_HEIGHT, z: 0 }), + visible: true, + }); +} + +function finishWarp() { + if (!warpActive) return; + warpActive = false; + Overlays.editOverlay(warpSphere, { + visible: false, + }); + Overlays.editOverlay(warpLine, { + visible: false, + }); + MyAvatar.position = warpPosition; +} + +function reportAxisValue(axis, newValue, oldValue) { + if (Math.abs(oldValue) < 0.2) oldValue = 0; + if (Math.abs(newValue) < 0.2) newValue = 0; + + if (axis == AXIS_FORWARD) { + moveDirection.z = newValue; + } else if (axis == AXIS_STRAFE) { + moveDirection.x = newValue; + } else if (axis == AXIS_ROTATE) { + if (oldValue == 0 && newValue != 0) { + var rotateRadians = newValue > 0 ? -ROTATE_INCREMENT : ROTATE_INCREMENT; + var orientation = MyAvatar.orientation; + orientation = Quat.multiply(Quat.fromPitchYawRollRadians(0, rotateRadians, 0), orientation) ; + MyAvatar.orientation = orientation; + } + } +} + +function reportButtonValue(button, newValue, oldValue) { + if (button == BUTTON_FLY_DOWN) { + flyDownButtonState = newValue; + } else if (button == BUTTON_FLY_UP) { + flyUpButtonState = newValue; + } else if (button == BUTTON_WARP) { + if (newValue) { + activateWarp(); + } else { + finishWarp(); + } + } else if (button == BUTTON_TURN_AROUND) { + if (newValue) { + MyAvatar.orientation = Quat.multiply( + Quat.fromPitchYawRollRadians(0, Math.PI, 0), MyAvatar.orientation); + } + } else if (newValue) { + var direction = null; + + if (button == BUTTON_WARP_FORWARD) { + direction = Quat.getFront(Camera.getOrientation()); + } else if (button == BUTTON_WARP_BACKWARD) { + direction = Quat.getFront(Camera.getOrientation()); + direction = Vec3.multiply(-1, direction); + } else if (button == BUTTON_WARP_LEFT) { + direction = Quat.getRight(Camera.getOrientation()); + direction = Vec3.multiply(-1, direction); + } else if (button == BUTTON_WARP_RIGHT) { + direction = Quat.getRight(Camera.getOrientation()); + } + + if (direction) { + direction.y = 0; + direction = Vec3.multiply(Vec3.normalize(direction), WARP_DISTANCE); + MyAvatar.position = Vec3.sum(MyAvatar.position, direction); + } + } + + if (flyUpButtonState && !flyDownButtonState) { + moveDirection.y = 1; + } else if (!flyUpButtonState && flyDownButtonState) { + moveDirection.y = -1; + } else { + moveDirection.y = 0; + } +} + +function update(dt) { + var velocity = { x: 0, y: 0, z: 0 }; + var move = copyVec3(moveDirection); + move.y = 0; + if (Vec3.length(move) > 0) { + velocity = Vec3.multiplyQbyV(Camera.getOrientation(), move); + velocity.y = 0; + velocity = Vec3.multiply(Vec3.normalize(velocity), MOVE_SPEED); + } + + if (moveDirection.y != 0) { + velocity.y = moveDirection.y * MOVE_SPEED; + } + + MyAvatar.setVelocity(velocity); + + updateWarp(); +} + +function addJoystick(gamepad) { + gamepad.axisValueChanged.connect(reportAxisValue); + gamepad.buttonStateChanged.connect(reportButtonValue); + + gamepads[gamepad.instanceId] = gamepad; + + print("Added gamepad: " + gamepad.name + " (" + gamepad.instanceId + ")"); +} + +function removeJoystick(gamepad) { + delete gamepads[gamepad.instanceId] + + print("Removed gamepad: " + gamepad.name + " (" + gamepad.instanceId + ")"); +} + +var allJoysticks = Joysticks.getAllJoysticks(); +for (var i = 0; i < allJoysticks.length; i++) { + addJoystick(allJoysticks[i]); +} + +Joysticks.joystickAdded.connect(addJoystick); +Joysticks.joystickRemoved.connect(removeJoystick); + +Script.update.connect(update); diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index fbac30e796..becc8c86b2 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -244,7 +244,7 @@ EntityPropertyDialogBox = (function () { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); - selectionDisplay.highlightSelectable(editModelID, propeties); + selectionDisplay.select(editModelID, false); } modelSelected = false; }); diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index d9cf2c54fd..503d4415de 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -16,6 +16,8 @@ Script.include("libraries/globals.js"); SelectionDisplay = (function () { var that = {}; + var MINIMUM_DIMENSION = 0.001; + var mode = "UNKNOWN"; var overlayNames = new Array(); var lastAvatarPosition = MyAvatar.position; @@ -31,13 +33,44 @@ SelectionDisplay = (function () { var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; + var rotateOverlayTargetSize = 10000; // really big target + var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool + var innerRadius; + var outerRadius; + var yawHandleRotation; + var pitchHandleRotation; + var rollHandleRotation; + var yawCenter; + var pitchCenter; + var rollCenter; + var yawZero; + var pitchZero; + var rollZero; + var yawNormal; + var pitchNormal; + var rollNormal; + var rotationNormal; + + var originalRotation; + var originalPitch; + var originalYaw; + var originalRoll; + + var rotateHandleColor = { red: 0, green: 0, blue: 0 }; var rotateHandleAlpha = 0.7; + var highlightedHandleColor = { red: 255, green: 0, blue: 0 }; + var highlightedHandleAlpha = 0.7; + var previousHandle = false; + var previousHandleColor; + var previousHandleAlpha; + var grabberSizeCorner = 0.025; var grabberSizeEdge = 0.015; var grabberSizeFace = 0.025; + var grabberAlpha = 1; var grabberColorCorner = { red: 120, green: 120, blue: 120 }; var grabberColorEdge = { red: 0, green: 0, blue: 0 }; var grabberColorFace = { red: 120, green: 120, blue: 120 }; @@ -151,7 +184,8 @@ SelectionDisplay = (function () { alpha: 0.5, solid: true, visible: false, - rotation: baseOverlayRotation + rotation: baseOverlayRotation, + ignoreRayIntersection: true, // always ignore this }); var yawOverlayAngles = { x: 90, y: 0, z: 0 }; @@ -161,6 +195,34 @@ SelectionDisplay = (function () { var rollOverlayAngles = { x: 0, y: 180, z: 0 }; var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); + var rotateZeroOverlay = Overlays.addOverlay("line3d", { + visible: false, + lineWidth: 2.0, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 255, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + var rotateCurrentOverlay = Overlays.addOverlay("line3d", { + visible: false, + lineWidth: 2.0, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 0, blue: 255 }, + ignoreRayIntersection: true, // always ignore this + }); + + + var rotateOverlayTarget = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: rotateOverlayTargetSize, + color: { red: 0, green: 0, blue: 0 }, + alpha: 0.0, + solid: true, + visible: false, + rotation: yawOverlayRotation, + }); var rotateOverlayInner = Overlays.addOverlay("circle3d", { position: { x:0, y: 0, z: 0}, @@ -171,12 +233,13 @@ SelectionDisplay = (function () { visible: false, rotation: yawOverlayRotation, hasTickMarks: true, - majorTickMarksAngle: 12.5, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, majorTickMarksLength: -0.25, minorTickMarksLength: 0, majorTickMarksColor: { red: 0, green: 0, blue: 0 }, minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -195,6 +258,7 @@ SelectionDisplay = (function () { minorTickMarksLength: 0.1, majorTickMarksColor: { red: 0, green: 0, blue: 0 }, minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -205,10 +269,14 @@ SelectionDisplay = (function () { solid: true, visible: false, rotation: yawOverlayRotation, + ignoreRayIntersection: true, // always ignore this + hasTickMarks: true, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, }); var yawHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -220,7 +288,7 @@ SelectionDisplay = (function () { var pitchHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -232,7 +300,7 @@ SelectionDisplay = (function () { var rollHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -279,11 +347,14 @@ SelectionDisplay = (function () { overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; + overlayNames[rotateOverlayTarget] = "rotateOverlayTarget"; overlayNames[rotateOverlayInner] = "rotateOverlayInner"; overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; - + overlayNames[rotateZeroOverlay] = "rotateZeroOverlay"; + overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay"; + that.cleanup = function () { Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); @@ -322,22 +393,21 @@ SelectionDisplay = (function () { Overlays.deleteOverlay(pitchHandle); Overlays.deleteOverlay(rollHandle); + Overlays.deleteOverlay(rotateOverlayTarget); Overlays.deleteOverlay(rotateOverlayInner); Overlays.deleteOverlay(rotateOverlayOuter); Overlays.deleteOverlay(rotateOverlayCurrent); + Overlays.deleteOverlay(rotateZeroOverlay); + Overlays.deleteOverlay(rotateCurrentOverlay); + + }; that.highlightSelectable = function(entityID) { var properties = Entities.getEntityProperties(entityID); - var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; - Overlays.editOverlay(highlightBox, - { - visible: true, - position: center, - dimensions: properties.dimensions, - rotation: properties.rotation, - }); + Overlays.editOverlay(highlightBox, { visible: true, position: properties.boundingBox.center, + dimensions: properties.boundingBox.dimensions }); }; that.unhighlightSelectable = function(entityID) { @@ -377,8 +447,8 @@ SelectionDisplay = (function () { var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); - var innerRadius = diagonal; - var outerRadius = diagonal * 1.15; + innerRadius = diagonal; + outerRadius = diagonal * 1.15; var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -391,31 +461,33 @@ SelectionDisplay = (function () { var rotateHandleOffset = 0.05; var grabberMoveUpOffset = 0.1; - var left = properties.position.x - halfDimensions.x; - var right = properties.position.x + halfDimensions.x; - var bottom = properties.position.y - halfDimensions.y; - var top = properties.position.y + halfDimensions.y; - var near = properties.position.z - halfDimensions.z; - var far = properties.position.z + halfDimensions.z; - var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; - var BLN = { x: left, y: bottom, z: near }; - var BRN = { x: right, y: bottom, z: near }; - var BLF = { x: left, y: bottom, z: far }; - var BRF = { x: right, y: bottom, z: far }; - var TLN = { x: left, y: top, z: near }; - var TRN = { x: right, y: top, z: near }; - var TLF = { x: left, y: top, z: far }; - var TRF = { x: right, y: top, z: far }; + objectCenter = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + + top = properties.boundingBox.tfl.y; + far = properties.boundingBox.tfl.z; + left = properties.boundingBox.tfl.x; + + bottom = properties.boundingBox.brn.y; + right = properties.boundingBox.brn.x; + near = properties.boundingBox.brn.z; + + boundsCenter = { x: properties.boundingBox.center.x, y: properties.boundingBox.center.y, z: properties.boundingBox.center.z }; + + BLN = { x: left, y: bottom, z: near }; + BRN = { x: right, y: bottom, z: near }; + BLF = { x: left, y: bottom, z: far }; + BRF = { x: right, y: bottom, z: far }; + TLN = { x: left, y: top, z: near }; + TRN = { x: right, y: top, z: near }; + TLF = { x: left, y: top, z: far }; + TRF = { x: right, y: top, z: far }; var yawCorner; var pitchCorner; var rollCorner; - var yawHandleRotation; - var pitchHandleRotation; - var rollHandleRotation; - // determine which bottom corner we are closest to /*------------------------------ example: @@ -429,124 +501,189 @@ SelectionDisplay = (function () { ------------------------------*/ - if (MyAvatar.position.x > center.x) { + if (MyAvatar.position.x > objectCenter.x) { // must be BRF or BRN - if (MyAvatar.position.z < center.z) { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 180 }); + if (MyAvatar.position.z < objectCenter.z) { - yawCorner = { x: right + rotateHandleOffset, + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 90, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 0 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }); + + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, z: near - rotateHandleOffset }; - pitchCorner = { x: right + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset }; - - rollCorner = { x: left - rotateHandleOffset, + pitchCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset}; + rollCorner = { x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset }; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z}; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far }; + + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" }); + + } else { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 270, z: 0 }); + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 0, z: 0 }); pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }); - yawCorner = { x: right + rotateHandleOffset, + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + + yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, z: far + rotateHandleOffset }; - pitchCorner = { x: left - rotateHandleOffset, + pitchCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset }; - rollCorner = { x: right + rotateHandleOffset, + rollCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset}; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near}; + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); } } else { + // must be BLF or BLN - if (MyAvatar.position.z < center.z) { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); + if (MyAvatar.position.z < objectCenter.z) { + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 180, z: 0 }); pitchHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 90 }); rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - yawCorner = { x: left - rotateHandleOffset, + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, z: near - rotateHandleOffset }; - pitchCorner = { x: right + rotateHandleOffset, + pitchCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset }; - rollCorner = { x: left - rotateHandleOffset, + rollCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset}; - - } else { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 180, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far}; - yawCorner = { x: left - rotateHandleOffset, + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + + } else { + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 270, z: 0 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + + yawNormal = { x: 0, y: 1, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + + yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, z: far + rotateHandleOffset }; - pitchCorner = { x: left - rotateHandleOffset, + rollCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset }; - rollCorner = { x: right + rotateHandleOffset, + pitchCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset}; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near }; + pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z}; + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + } } + + var rotateHandlesVisible = true; + var translateHandlesVisible = true; + var stretchHandlesVisible = true; + var selectionBoxVisible = true; + if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") { + rotateHandlesVisible = false; + translateHandlesVisible = false; + stretchHandlesVisible = false; + selectionBoxVisible = false; + } else if (mode == "TRANSLATE_UP_DOWN") { + rotateHandlesVisible = false; + stretchHandlesVisible = false; + } else if (mode != "UNKNOWN") { + // every other mode is a stretch mode... + rotateHandlesVisible = false; + translateHandlesVisible = false; + } Overlays.editOverlay(highlightBox, { visible: false }); - Overlays.editOverlay(selectionBox, - { - visible: true, - position: center, - dimensions: properties.dimensions, - rotation: properties.rotation, - }); + Overlays.editOverlay(selectionBox, { visible: selectionBoxVisible, position: properties.boundingBox.center, + dimensions: properties.boundingBox.dimensions }); - Overlays.editOverlay(grabberMoveUp, { visible: true, position: { x: center.x, y: top + grabberMoveUpOffset, z: center.z } }); + Overlays.editOverlay(grabberMoveUp, { visible: translateHandlesVisible, position: { x: boundsCenter.x, y: top + grabberMoveUpOffset, z: boundsCenter.z } }); - Overlays.editOverlay(grabberLBN, { visible: true, position: { x: left, y: bottom, z: near } }); - Overlays.editOverlay(grabberRBN, { visible: true, position: { x: right, y: bottom, z: near } }); - Overlays.editOverlay(grabberLBF, { visible: true, position: { x: left, y: bottom, z: far } }); - Overlays.editOverlay(grabberRBF, { visible: true, position: { x: right, y: bottom, z: far } }); - Overlays.editOverlay(grabberLTN, { visible: true, position: { x: left, y: top, z: near } }); - Overlays.editOverlay(grabberRTN, { visible: true, position: { x: right, y: top, z: near } }); - Overlays.editOverlay(grabberLTF, { visible: true, position: { x: left, y: top, z: far } }); - Overlays.editOverlay(grabberRTF, { visible: true, position: { x: right, y: top, z: far } }); + Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: near } }); + Overlays.editOverlay(grabberRBN, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: near } }); + Overlays.editOverlay(grabberLBF, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: far } }); + Overlays.editOverlay(grabberRBF, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: far } }); + Overlays.editOverlay(grabberLTN, { visible: stretchHandlesVisible, position: { x: left, y: top, z: near } }); + Overlays.editOverlay(grabberRTN, { visible: stretchHandlesVisible, position: { x: right, y: top, z: near } }); + Overlays.editOverlay(grabberLTF, { visible: stretchHandlesVisible, position: { x: left, y: top, z: far } }); + Overlays.editOverlay(grabberRTF, { visible: stretchHandlesVisible, position: { x: right, y: top, z: far } }); - Overlays.editOverlay(grabberTOP, { visible: true, position: { x: center.x, y: top, z: center.z } }); - Overlays.editOverlay(grabberBOTTOM, { visible: true, position: { x: center.x, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberLEFT, { visible: true, position: { x: left, y: center.y, z: center.z } }); - Overlays.editOverlay(grabberRIGHT, { visible: true, position: { x: right, y: center.y, z: center.z } }); - Overlays.editOverlay(grabberNEAR, { visible: true, position: { x: center.x, y: center.y, z: near } }); - Overlays.editOverlay(grabberFAR, { visible: true, position: { x: center.x, y: center.y, z: far } }); + Overlays.editOverlay(grabberTOP, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberBOTTOM, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberLEFT, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: boundsCenter.z } }); + Overlays.editOverlay(grabberRIGHT, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: boundsCenter.z } }); + Overlays.editOverlay(grabberNEAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberFAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: far } }); - Overlays.editOverlay(grabberEdgeTR, { visible: true, position: { x: right, y: top, z: center.z } }); - Overlays.editOverlay(grabberEdgeTL, { visible: true, position: { x: left, y: top, z: center.z } }); - Overlays.editOverlay(grabberEdgeTF, { visible: true, position: { x: center.x, y: top, z: far } }); - Overlays.editOverlay(grabberEdgeTN, { visible: true, position: { x: center.x, y: top, z: near } }); - Overlays.editOverlay(grabberEdgeBR, { visible: true, position: { x: right, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberEdgeBL, { visible: true, position: { x: left, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberEdgeBF, { visible: true, position: { x: center.x, y: bottom, z: far } }); - Overlays.editOverlay(grabberEdgeBN, { visible: true, position: { x: center.x, y: bottom, z: near } }); - Overlays.editOverlay(grabberEdgeNR, { visible: true, position: { x: right, y: center.y, z: near } }); - Overlays.editOverlay(grabberEdgeNL, { visible: true, position: { x: left, y: center.y, z: near } }); - Overlays.editOverlay(grabberEdgeFR, { visible: true, position: { x: right, y: center.y, z: far } }); - Overlays.editOverlay(grabberEdgeFL, { visible: true, position: { x: left, y: center.y, z: far } }); + Overlays.editOverlay(grabberEdgeTR, { visible: stretchHandlesVisible, position: { x: right, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeTL, { visible: stretchHandlesVisible, position: { x: left, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeTF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: far } }); + Overlays.editOverlay(grabberEdgeTN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: near } }); + Overlays.editOverlay(grabberEdgeBR, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeBL, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeBF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: far } }); + Overlays.editOverlay(grabberEdgeBN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: near } }); + Overlays.editOverlay(grabberEdgeNR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberEdgeNL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberEdgeFR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: far } }); + Overlays.editOverlay(grabberEdgeFL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: far } }); Overlays.editOverlay(baseOfEntityProjectionOverlay, @@ -562,14 +699,12 @@ SelectionDisplay = (function () { rotation: properties.rotation, }); + + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); Overlays.editOverlay(rotateOverlayInner, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: innerRadius, innerRadius: 0.9, alpha: innerAlpha @@ -578,10 +713,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(rotateOverlayOuter, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: outerRadius, innerRadius: 0.9, startAt: 0, @@ -592,20 +723,19 @@ SelectionDisplay = (function () { Overlays.editOverlay(rotateOverlayCurrent, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: outerRadius, startAt: 0, endAt: 0, innerRadius: 0.9, }); + + Overlays.editOverlay(rotateZeroOverlay, { visible: false }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: false }); // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden - Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation}); + Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: rotateHandlesVisible, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: rotateHandlesVisible, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; @@ -655,10 +785,14 @@ SelectionDisplay = (function () { Overlays.editOverlay(pitchHandle, { visible: false }); Overlays.editOverlay(rollHandle, { visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); Overlays.editOverlay(rotateOverlayInner, { visible: false }); Overlays.editOverlay(rotateOverlayOuter, { visible: false }); Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + Overlays.editOverlay(rotateZeroOverlay, { visible: false }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: false }); + Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); currentSelection = { id: -1, isKnownID: false }; @@ -762,6 +896,11 @@ SelectionDisplay = (function () { // dimensions changes by: (oldNEAR - newNEAR) var changeInDimensions = { x: 0, y: 0, z: (oldNEAR - newNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: 0, y: 0, z: (oldNEAR - newNEAR) * -0.5 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -807,6 +946,11 @@ SelectionDisplay = (function () { var newFAR = oldFAR + vector.z; var changeInDimensions = { x: 0, y: 0, z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: 0, y: 0, z: (newFAR - oldFAR) * 0.5 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -852,6 +996,11 @@ SelectionDisplay = (function () { var newTOP = oldTOP + vector.y; var changeInDimensions = { x: 0, y: (newTOP - oldTOP), z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + var changeInPosition = { x: 0, y: (newTOP - oldTOP) * 0.5, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -896,6 +1045,11 @@ SelectionDisplay = (function () { var newBOTTOM = oldBOTTOM + vector.y; var changeInDimensions = { x: 0, y: (oldBOTTOM - newBOTTOM), z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + var changeInPosition = { x: 0, y: (oldBOTTOM - newBOTTOM) * -0.5, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -940,6 +1094,11 @@ SelectionDisplay = (function () { var newRIGHT = oldRIGHT + vector.x; var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: 0 , z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: 0, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -984,6 +1143,11 @@ SelectionDisplay = (function () { var newLEFT = oldLEFT + vector.x; var changeInDimensions = { x: (oldLEFT - newLEFT), y: 0, z: 0 }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (oldLEFT - newLEFT) * -0.5, y: 0, z: 0 }; var newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, changeInPosition); var wantDebug = false; @@ -1036,6 +1200,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newBOTTOM - oldBOTTOM) , z: (newNEAR - oldNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: (newBOTTOM - oldBOTTOM) * -0.5, z: (newNEAR - oldNEAR) * -0.5 }; @@ -1090,6 +1267,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newBOTTOM - oldBOTTOM) , z: (newNEAR - oldNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, y: (newBOTTOM - oldBOTTOM) * -0.5, z: (newNEAR - oldNEAR) * -0.5 }; @@ -1144,6 +1334,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newTOP - oldTOP) , z: (newNEAR - oldNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: (newTOP - oldTOP) * 0.5, z: (newNEAR - oldNEAR) * -0.5 }; @@ -1198,6 +1401,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newTOP - oldTOP) , z: (newNEAR - oldNEAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, y: (newTOP - oldTOP) * 0.5, z: (newNEAR - oldNEAR) * -0.5 }; @@ -1252,6 +1468,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newBOTTOM - oldBOTTOM) , z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: (newBOTTOM - oldBOTTOM) * -0.5, z: (newFAR - oldFAR) * 0.5 }; @@ -1306,6 +1535,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newBOTTOM - oldBOTTOM) , z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, y: (newBOTTOM - oldBOTTOM) * -0.5, z: (newFAR - oldFAR) * 0.5 }; @@ -1360,6 +1602,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newRIGHT - oldRIGHT), y: (newTOP - oldTOP) , z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newRIGHT - oldRIGHT) * 0.5, y: (newTOP - oldTOP) * 0.5, z: (newFAR - oldFAR) * 0.5 }; @@ -1414,6 +1669,19 @@ SelectionDisplay = (function () { var changeInDimensions = { x: (newLEFT - oldLEFT), y: (newTOP - oldTOP) , z: (newFAR - oldFAR) }; var newDimensions = Vec3.sum(selectedEntityPropertiesOriginalDimensions, changeInDimensions); + + if (newDimensions.x < MINIMUM_DIMENSION) { + newDimensions.x = MINIMUM_DIMENSION; + } + + if (newDimensions.y < MINIMUM_DIMENSION) { + newDimensions.y = MINIMUM_DIMENSION; + } + + if (newDimensions.z < MINIMUM_DIMENSION) { + newDimensions.z = MINIMUM_DIMENSION; + } + var changeInPosition = { x: (newLEFT - oldLEFT) * -0.5, y: (newTOP - oldTOP) * 0.5, z: (newFAR - oldFAR) * 0.5 }; @@ -1440,7 +1708,246 @@ SelectionDisplay = (function () { Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); that.select(currentSelection, false); // TODO: this should be more than highlighted - }; + }; + + that.rotateYaw = function(event) { + if (!entitySelected || mode !== "ROTATE_YAW") { + return; // not allowed + } + + var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); + + if (debug) { + print("rotateYaw()..."); + print(" event.x,y:" + event.x + "," + event.y); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + + var result = Overlays.findRayIntersection(pickRay); + + if (debug) { + print(" findRayIntersection() .... result.intersects:" + result.intersects); + } + + if (result.intersects) { + + + var properties = Entities.getEntityProperties(currentSelection); + var center = yawCenter; + var zero = yawZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + if (debug) { + Vec3.print(" result.intersection:",result.intersection); + Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + print(" angleFromZero:" + angleFromZero); + } + + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); + var newRotation = Quat.multiply(yawChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, + majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + } + + } + }; + + that.rotatePitch = function(event) { + if (!entitySelected || mode !== "ROTATE_PITCH") { + return; // not allowed + } + var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); + + if (debug) { + print("rotatePitch()..."); + print(" event.x,y:" + event.x + "," + event.y); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + var result = Overlays.findRayIntersection(pickRay); + + if (debug) { + print(" findRayIntersection() .... result.intersects:" + result.intersects); + } + + if (result.intersects) { + var properties = Entities.getEntityProperties(currentSelection); + var center = pitchCenter; + var zero = pitchZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + if (debug) { + Vec3.print(" result.intersection:",result.intersection); + Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + print(" angleFromZero:" + angleFromZero); + } + + var pitchChange = Quat.fromVec3Degrees({ x: angleFromZero, y: 0, z: 0 }); + var newRotation = Quat.multiply(pitchChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, + majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + } + } + }; + + that.rotateRoll = function(event) { + if (!entitySelected || mode !== "ROTATE_ROLL") { + return; // not allowed + } + var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); + + if (debug) { + print("rotateRoll()..."); + print(" event.x,y:" + event.x + "," + event.y); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + var result = Overlays.findRayIntersection(pickRay); + + if (debug) { + print(" findRayIntersection() .... result.intersects:" + result.intersects); + } + + if (result.intersects) { + var properties = Entities.getEntityProperties(currentSelection); + var center = rollCenter; + var zero = rollZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + if (debug) { + Vec3.print(" result.intersection:",result.intersection); + Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + print(" angleFromZero:" + angleFromZero); + } + + var rollChange = Quat.fromVec3Degrees({ x: 0, y: 0, z: angleFromZero }); + var newRotation = Quat.multiply(rollChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, + majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + } + } + }; that.checkMove = function() { if (currentSelection.isKnownID && @@ -1450,6 +1957,7 @@ SelectionDisplay = (function () { }; that.mousePressEvent = function(event) { + var somethingClicked = false; var pickRay = Camera.computePickRay(event.x, event.y); @@ -1477,6 +1985,36 @@ SelectionDisplay = (function () { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; somethingClicked = true; + + // in translate mode, we hide our stretch handles... + Overlays.editOverlay(grabberLBN, { visible: false }); + Overlays.editOverlay(grabberLBF, { visible: false }); + Overlays.editOverlay(grabberRBN, { visible: false }); + Overlays.editOverlay(grabberRBF, { visible: false }); + Overlays.editOverlay(grabberLTN, { visible: false }); + Overlays.editOverlay(grabberLTF, { visible: false }); + Overlays.editOverlay(grabberRTN, { visible: false }); + Overlays.editOverlay(grabberRTF, { visible: false }); + + Overlays.editOverlay(grabberTOP, { visible: false }); + Overlays.editOverlay(grabberBOTTOM, { visible: false }); + Overlays.editOverlay(grabberLEFT, { visible: false }); + Overlays.editOverlay(grabberRIGHT, { visible: false }); + Overlays.editOverlay(grabberNEAR, { visible: false }); + Overlays.editOverlay(grabberFAR, { visible: false }); + + Overlays.editOverlay(grabberEdgeTR, { visible: false }); + Overlays.editOverlay(grabberEdgeTL, { visible: false }); + Overlays.editOverlay(grabberEdgeTF, { visible: false }); + Overlays.editOverlay(grabberEdgeTN, { visible: false }); + Overlays.editOverlay(grabberEdgeBR, { visible: false }); + Overlays.editOverlay(grabberEdgeBL, { visible: false }); + Overlays.editOverlay(grabberEdgeBF, { visible: false }); + Overlays.editOverlay(grabberEdgeBN, { visible: false }); + Overlays.editOverlay(grabberEdgeNR, { visible: false }); + Overlays.editOverlay(grabberEdgeNL, { visible: false }); + Overlays.editOverlay(grabberEdgeFR, { visible: false }); + Overlays.editOverlay(grabberEdgeFL, { visible: false }); break; case grabberRBN: @@ -1559,20 +2097,134 @@ SelectionDisplay = (function () { } } + // if one of the items above was clicked, then we know we are in translate or stretch mode, and we + // should hide our rotate handles... + if (somethingClicked) { + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + + if (mode != "TRANSLATE_UP_DOWN") { + Overlays.editOverlay(grabberMoveUp, { visible: false }); + } + } + if (!somethingClicked) { + + print("rotate handle case..."); + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); var result = Overlays.findRayIntersection(pickRay); + + var overlayOrientation; + var overlayCenter; + + var properties = Entities.getEntityProperties(currentSelection); + var angles = Quat.safeEulerAngles(properties.rotation); + var pitch = angles.x; + var yaw = angles.y; + var roll = angles.z; + + originalRotation = properties.rotation; + originalPitch = pitch; + originalYaw = yaw; + originalRoll = roll; + if (result.intersects) { switch(result.overlayID) { + case yawHandle: + mode = "ROTATE_YAW"; + somethingClicked = true; + overlayOrientation = yawHandleRotation; + overlayCenter = yawCenter; + yawZero = result.intersection; + rotationNormal = yawNormal; + break; + + case pitchHandle: + mode = "ROTATE_PITCH"; + somethingClicked = true; + overlayOrientation = pitchHandleRotation; + overlayCenter = pitchCenter; + pitchZero = result.intersection; + rotationNormal = pitchNormal; + break; + + case rollHandle: + mode = "ROTATE_ROLL"; + somethingClicked = true; + overlayOrientation = rollHandleRotation; + overlayCenter = rollCenter; + rollZero = result.intersection; + rotationNormal = rollNormal; + break; + default: print("mousePressEvent()...... " + overlayNames[result.overlayID]); mode = "UNKNOWN"; break; } } + + print(" somethingClicked:" + somethingClicked); + print(" mode:" + mode); + + + if (somethingClicked) { + + Overlays.editOverlay(rotateOverlayTarget, { visible: true, rotation: overlayOrientation, position: overlayCenter }); + Overlays.editOverlay(rotateOverlayInner, { visible: true, rotation: overlayOrientation, position: overlayCenter }); + Overlays.editOverlay(rotateOverlayOuter, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 0 }); + + // for debugging + var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); + if (debug) { + Overlays.editOverlay(rotateZeroOverlay, { visible: true, start: overlayCenter, end: result.intersection }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: overlayCenter, end: result.intersection }); + } + + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + + + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + Overlays.editOverlay(grabberMoveUp, { visible: false }); + Overlays.editOverlay(grabberLBN, { visible: false }); + Overlays.editOverlay(grabberLBF, { visible: false }); + Overlays.editOverlay(grabberRBN, { visible: false }); + Overlays.editOverlay(grabberRBF, { visible: false }); + Overlays.editOverlay(grabberLTN, { visible: false }); + Overlays.editOverlay(grabberLTF, { visible: false }); + Overlays.editOverlay(grabberRTN, { visible: false }); + Overlays.editOverlay(grabberRTF, { visible: false }); + + Overlays.editOverlay(grabberTOP, { visible: false }); + Overlays.editOverlay(grabberBOTTOM, { visible: false }); + Overlays.editOverlay(grabberLEFT, { visible: false }); + Overlays.editOverlay(grabberRIGHT, { visible: false }); + Overlays.editOverlay(grabberNEAR, { visible: false }); + Overlays.editOverlay(grabberFAR, { visible: false }); + + Overlays.editOverlay(grabberEdgeTR, { visible: false }); + Overlays.editOverlay(grabberEdgeTL, { visible: false }); + Overlays.editOverlay(grabberEdgeTF, { visible: false }); + Overlays.editOverlay(grabberEdgeTN, { visible: false }); + Overlays.editOverlay(grabberEdgeBR, { visible: false }); + Overlays.editOverlay(grabberEdgeBL, { visible: false }); + Overlays.editOverlay(grabberEdgeBF, { visible: false }); + Overlays.editOverlay(grabberEdgeBN, { visible: false }); + Overlays.editOverlay(grabberEdgeNR, { visible: false }); + Overlays.editOverlay(grabberEdgeNL, { visible: false }); + Overlays.editOverlay(grabberEdgeFR, { visible: false }); + Overlays.editOverlay(grabberEdgeFL, { visible: false }); + } } if (!somethingClicked) { @@ -1609,11 +2261,22 @@ SelectionDisplay = (function () { Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); + + return somethingClicked; }; that.mouseMoveEvent = function(event) { //print("mouseMoveEvent()... mode:" + mode); switch (mode) { + case "ROTATE_YAW": + that.rotateYaw(event); + break; + case "ROTATE_PITCH": + that.rotatePitch(event); + break; + case "ROTATE_ROLL": + that.rotateRoll(event); + break; case "TRANSLATE_UP_DOWN": that.translateUpDown(event); break; @@ -1665,24 +2328,135 @@ SelectionDisplay = (function () { that.stretchLEFT(event); break; default: - // nothing to do by default - break; + // if not in any specific mode, then just look for handles to highlight... + var pickRay = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay); + var pickedColor; + var pickedAlpha; + var highlightNeeded = false; + + if (result.intersects) { + switch(result.overlayID) { + case yawHandle: + case pitchHandle: + case rollHandle: + pickedColor = rotateHandleColor; + pickedAlpha = rotateHandleAlpha; + highlightNeeded = true; + break; + + case grabberMoveUp: + pickedColor = rotateHandleColor; + pickedAlpha = rotateHandleAlpha; + highlightNeeded = true; + break; + + case grabberLBN: + case grabberLBF: + case grabberRBN: + case grabberRBF: + case grabberLTN: + case grabberLTF: + case grabberRTN: + case grabberRTF: + pickedColor = grabberColorCorner; + pickedAlpha = grabberAlpha; + highlightNeeded = true; + break; + + case grabberTOP: + case grabberBOTTOM: + case grabberLEFT: + case grabberRIGHT: + case grabberNEAR: + case grabberFAR: + pickedColor = grabberColorFace; + pickedAlpha = grabberAlpha; + highlightNeeded = true; + break; + + case grabberEdgeTR: + case grabberEdgeTL: + case grabberEdgeTF: + case grabberEdgeTN: + case grabberEdgeBR: + case grabberEdgeBL: + case grabberEdgeBF: + case grabberEdgeBN: + case grabberEdgeNR: + case grabberEdgeNL: + case grabberEdgeFR: + case grabberEdgeFL: + pickedColor = grabberColorEdge; + pickedAlpha = grabberAlpha; + highlightNeeded = true; + break; + + default: + if (previousHandle) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + previousHandle = false; + } + break; + } + + if (highlightNeeded) { + if (previousHandle) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + previousHandle = false; + } + Overlays.editOverlay(result.overlayID, { color: highlightedHandleColor, alpha: highlightedHandleAlpha }); + previousHandle = result.overlayID; + previousHandleColor = pickedColor; + previousHandleAlpha = pickedAlpha; + } + + } else { + if (previousHandle) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + previousHandle = false; + } + } + + return false; } + return true; }; that.mouseReleaseEvent = function(event) { + var showHandles = false; + // hide our rotation overlays..., and show our handles + if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + showHandles = true; + } + + if (mode != "UNKNOWN") { + showHandles = true; + } + mode = "UNKNOWN"; // if something is selected, then reset the "original" properties for any potential next click+move operation if (entitySelected) { + + if (showHandles) { + that.select(currentSelection, event); + } + selectedEntityProperties = Entities.getEntityProperties(currentSelection); selectedEntityPropertiesOriginalPosition = properties.position; selectedEntityPropertiesOriginalDimensions = properties.dimensions; } + }; - Controller.mousePressEvent.connect(that.mousePressEvent); - Controller.mouseMoveEvent.connect(that.mouseMoveEvent); + // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: + // Controller.mousePressEvent.connect(that.mousePressEvent); + // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); return that; diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index 8e82e2e2b1..195dc8c677 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -262,7 +262,6 @@ var toolBar = (function () { if (clickedOverlay === loadFileMenuItem) { toggleNewModelButton(false); - // TODO BUG: this is bug, if the user has never uploaded a model, this will throw an JS exception file = Window.browse("Select your model file ...", Settings.getValue("LastModelUploadLocation").path(), "Model files (*.fst *.fbx)"); @@ -362,13 +361,15 @@ function rayPlaneIntersection(pickRay, point, normal) { function mousePressEvent(event) { mouseLastPosition = { x: event.x, y: event.y }; - entitySelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { // Event handled; do nothing. return; } else { + entitySelected = false; + selectionDisplay.unselectAll(); + // If we aren't active and didn't click on an overlay: quit if (!isActive) { return; @@ -464,34 +465,36 @@ function mouseMoveEvent(event) { if (!isActive) { return; } + + // allow the selectionDisplay to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event)) { + return; + } var pickRay = Camera.computePickRay(event.x, event.y); - if (!entitySelected) { - var entityIntersection = Entities.findRayIntersection(pickRay); - if (entityIntersection.accurate) { - if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) { - selectionDisplay.unhighlightSelectable(highlightedEntityID); - highlightedEntityID = { id: -1, isKnownID: false }; - } - - var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), - entityIntersection.properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (entityIntersection.entityID.isKnownID && sizeOK) { - if (wantEntityGlow) { - Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); - } - highlightedEntityID = entityIntersection.entityID; - selectionDisplay.highlightSelectable(entityIntersection.entityID); - } - + var entityIntersection = Entities.findRayIntersection(pickRay); + if (entityIntersection.accurate) { + if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) { + selectionDisplay.unhighlightSelectable(highlightedEntityID); + highlightedEntityID = { id: -1, isKnownID: false }; } - return; + + var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), + entityIntersection.properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (entityIntersection.entityID.isKnownID && sizeOK) { + if (wantEntityGlow) { + Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); + } + highlightedEntityID = entityIntersection.entityID; + selectionDisplay.highlightSelectable(entityIntersection.entityID); + } + } } @@ -538,6 +541,7 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); + Menu.addMenuItem({ menuName: "Developer", menuItemName: "Debug Ryans Rotation Problems", isCheckable: true }); } setupModelMenus(); // do this when first running our script. @@ -557,6 +561,7 @@ function cleanupModelMenus() { Menu.removeSeparator("File", "Models"); Menu.removeMenuItem("File", "Export Models"); Menu.removeMenuItem("File", "Import Models"); + Menu.removeMenuItem("Developer", "Debug Ryans Rotation Problems"); } Script.scriptEnding.connect(function() { @@ -594,6 +599,19 @@ function handeMenuEvent(menuItem) { } } else if (menuItem == "Edit Properties...") { // good place to put the properties dialog + + editModelID = -1; + if (entitySelected) { + print(" Edit Properties.... selectedEntityID="+ selectedEntityID); + editModelID = selectedEntityID; + } else { + print(" Edit Properties.... not holding..."); + } + if (editModelID != -1) { + print(" Edit Properties.... about to edit properties..."); + entityPropertyDialogBox.openDialog(editModelID); + } + } else if (menuItem == "Paste Models") { modelImporter.paste(); } else if (menuItem == "Export Models") { diff --git a/examples/xbox.js b/examples/xbox.js deleted file mode 100644 index 603e0dbf56..0000000000 --- a/examples/xbox.js +++ /dev/null @@ -1,19 +0,0 @@ -// -// xbox.js -// examples -// -// Created by Stephen Birarda on September 23, 2014 -// -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -gamepad = Joysticks.joystickWithName("Wireless 360 Controller"); - -function reportAxisValue(axis, newValue, oldValue) { - print("The value for axis " + axis + " has changed to " + newValue + ". It was " + oldValue); -} - -gamepad.axisValueChanged.connect(reportAxisValue); \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 19f8ef6d5f..16ca977bae 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL") +set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL2") foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) @@ -41,7 +41,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities) +foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities gpu) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) diff --git a/interface/resources/shaders/metavoxel_voxel_cursor.frag b/interface/resources/shaders/metavoxel_voxel_cursor.frag new file mode 100644 index 0000000000..88c6df6618 --- /dev/null +++ b/interface/resources/shaders/metavoxel_voxel_cursor.frag @@ -0,0 +1,32 @@ +#version 120 + +// +// metavoxel_voxel_cursor.frag +// fragment shader +// +// Created by Andrzej Kapolka on 10/10/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the inner radius of the outline, squared +const float SQUARED_OUTLINE_INNER_RADIUS = 0.81; + +// the outer radius of the outline, squared +const float SQUARED_OUTLINE_OUTER_RADIUS = 1.0; + +// the inner radius of the inset, squared +const float SQUARED_INSET_INNER_RADIUS = 0.855625; + +// the outer radius of the inset, squared +const float SQUARED_INSET_OUTER_RADIUS = 0.950625; + +void main(void) { + // use the distance to compute the ring color, then multiply it by the varying color + float squaredDistance = dot(gl_TexCoord[0].stp, gl_TexCoord[0].stp); + float alpha = step(SQUARED_OUTLINE_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_OUTLINE_OUTER_RADIUS); + float white = step(SQUARED_INSET_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_INSET_OUTER_RADIUS); + gl_FragColor = gl_Color * vec4(white, white, white, alpha); +} diff --git a/interface/resources/shaders/metavoxel_voxel_cursor.vert b/interface/resources/shaders/metavoxel_voxel_cursor.vert new file mode 100644 index 0000000000..586eef275a --- /dev/null +++ b/interface/resources/shaders/metavoxel_voxel_cursor.vert @@ -0,0 +1,25 @@ +#version 120 + +// +// metavoxel_voxel_cursor.vert +// vertex shader +// +// Created by Andrzej Kapolka on 10/10/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +void main(void) { + // compute the view space coordinates + vec4 viewPosition = gl_ModelViewMatrix * gl_Vertex; + gl_Position = gl_ProjectionMatrix * viewPosition; + + // generate the texture coordinates from the view position + gl_TexCoord[0] = vec4(dot(viewPosition, gl_EyePlaneS[4]), dot(viewPosition, gl_EyePlaneT[4]), + dot(viewPosition, gl_EyePlaneR[4]), 1.0); + + // copy the color for interpolation + gl_FrontColor = gl_Color; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 718360864a..777f78c2c1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -187,8 +187,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // set the associated application properties applicationInfo.beginGroup("INFO"); - qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); - setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); @@ -207,6 +205,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : qInstallMessageHandler(messageHandler); + qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); + // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); @@ -605,10 +605,14 @@ void Application::paintGL() { if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { if (!OculusManager::isConnected()) { + // If there isn't an HMD, match exactly to avatar's head _myCamera.setPosition(_myAvatar->getHead()->getEyePosition()); _myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation()); + } else { + // For an HMD, set the base position and orientation to that of the avatar body + _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); + _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); } - // OculusManager::display() updates camera position and rotation a bit further on. } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { static const float THIRD_PERSON_CAMERA_DISTANCE = 1.5f; @@ -664,7 +668,6 @@ void Application::paintGL() { _viewFrustumOffsetCamera.setRotation(_myCamera.getRotation() * frustumRotation); - _viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation _viewFrustumOffsetCamera.update(1.f/_fps); whichCamera = &_viewFrustumOffsetCamera; } @@ -1086,6 +1089,9 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Equal: _myAvatar->resetSize(); break; + case Qt::Key_Escape: + OculusManager::abandonCalibration(); + break; default: event->ignore(); break; @@ -1478,6 +1484,9 @@ void Application::setEnableVRMode(bool enableVRMode) { OculusManager::disconnect(); OculusManager::connect(); } + OculusManager::recalibrate(); + } else { + OculusManager::abandonCalibration(); } resizeGL(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); @@ -1542,10 +1551,9 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox FaceTracker* Application::getActiveFaceTracker() { return (_dde.isActive() ? static_cast(&_dde) : - (_cara.isActive() ? static_cast(&_cara) : (_faceshift.isActive() ? static_cast(&_faceshift) : (_faceplus.isActive() ? static_cast(&_faceplus) : - (_visage.isActive() ? static_cast(&_visage) : NULL))))); + (_visage.isActive() ? static_cast(&_visage) : NULL)))); } struct SendVoxelsOperationArgs { @@ -2004,19 +2012,6 @@ void Application::updateDDE() { _dde.update(); } -void Application::updateCara() { - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateCara()"); - - // Update Cara - _cara.update(); - - // Copy angular velocity if measured by cara, to the head - if (_cara.isActive()) { - _myAvatar->getHead()->setAngularVelocity(_cara.getHeadAngularVelocity()); - } -} - void Application::updateMyAvatarLookAtPosition() { PerformanceTimer perfTimer("lookAt"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); @@ -2116,7 +2111,6 @@ void Application::updateMetavoxels(float deltaTime) { } void Application::cameraMenuChanged() { - float modeShiftPeriod = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? 0.0f : 1.0f; if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); diff --git a/interface/src/Application.h b/interface/src/Application.h index 248c4325bf..ce9ed0c1a6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -63,7 +63,6 @@ #include "devices/PrioVR.h" #include "devices/SixenseManager.h" #include "devices/Visage.h" -#include "devices/CaraFaceTracker.h" #include "devices/DdeFaceTracker.h" #include "entities/EntityTreeRenderer.h" #include "particles/ParticleTreeRenderer.h" @@ -219,7 +218,6 @@ public: Faceshift* getFaceshift() { return &_faceshift; } Visage* getVisage() { return &_visage; } DdeFaceTracker* getDDE() { return &_dde; } - CaraFaceTracker* getCara() { return &_cara; } FaceTracker* getActiveFaceTracker(); PrioVR* getPrioVR() { return &_prioVR; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } @@ -365,6 +363,8 @@ public slots: void domainSettingsReceived(const QJsonObject& domainSettingsObject); + void resetSensors(); + private slots: void timer(); void idle(); @@ -381,7 +381,6 @@ private slots: void closeMirrorView(); void restoreMirrorView(); void shrinkMirrorView(); - void resetSensors(); void parseVersionXml(); @@ -407,7 +406,6 @@ private: void updateFaceshift(); void updateVisage(); void updateDDE(); - void updateCara(); void updateMyAvatarLookAtPosition(); void updateThreads(float deltaTime); void updateMetavoxels(float deltaTime); @@ -508,7 +506,6 @@ private: Faceplus _faceplus; Faceshift _faceshift; Visage _visage; - CaraFaceTracker _cara; DdeFaceTracker _dde; PrioVR _prioVR; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 79d66568bf..a8138363fa 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -22,13 +22,16 @@ Camera::Camera() : - _needsToInitialize(true), _mode(CAMERA_MODE_THIRD_PERSON), _position(0.0f, 0.0f, 0.0f), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), _aspectRatio(16.0f/9.0f), _nearClip(DEFAULT_NEAR_CLIP), // default _farClip(DEFAULT_FAR_CLIP), // default + _hmdPosition(), + _hmdRotation(), + _targetPosition(), + _targetRotation(), _scale(1.0f) { } @@ -64,26 +67,6 @@ void Camera::setFarClip(float f) { _farClip = f; } -void Camera::setEyeOffsetPosition(const glm::vec3& p) { - _eyeOffsetPosition = p; -} - -void Camera::setEyeOffsetOrientation(const glm::quat& o) { - _eyeOffsetOrientation = o; - -} - -void Camera::setScale(float s) { - _scale = s; - _needsToInitialize = true; - -} - -void Camera::initialize() { - _needsToInitialize = true; -} - - CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) : _camera(camera), _viewFrustum(viewFrustum) { diff --git a/interface/src/Camera.h b/interface/src/Camera.h index ee4930272d..80454a969e 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -38,17 +38,23 @@ public: void setPosition(const glm::vec3& p) { _position = p; } void setRotation(const glm::quat& rotation) { _rotation = rotation; }; + void setHmdPosition(const glm::vec3& hmdPosition) { _hmdPosition = hmdPosition; } + void setHmdRotation(const glm::quat& hmdRotation) { _hmdRotation = hmdRotation; }; + void setMode(CameraMode m); void setFieldOfView(float f); void setAspectRatio(float a); void setNearClip(float n); void setFarClip(float f); - void setEyeOffsetPosition(const glm::vec3& p); - void setEyeOffsetOrientation(const glm::quat& o); - void setScale(const float s); + void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; } + void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } + void setScale(const float s) { _scale = s; } + + glm::vec3 getPosition() const { return _position + _hmdPosition; } + glm::quat getRotation() const { return _rotation * _hmdRotation; } + const glm::vec3& getHmdPosition() const { return _hmdPosition; } + const glm::quat& getHmdRotation() const { return _hmdRotation; } - const glm::vec3& getPosition() const { return _position; } - const glm::quat& getRotation() const { return _rotation; } CameraMode getMode() const { return _mode; } float getFieldOfView() const { return _fieldOfView; } float getAspectRatio() const { return _aspectRatio; } @@ -60,7 +66,6 @@ public: private: - bool _needsToInitialize; CameraMode _mode; glm::vec3 _position; float _fieldOfView; // degrees @@ -70,7 +75,10 @@ private: glm::vec3 _eyeOffsetPosition; glm::quat _eyeOffsetOrientation; glm::quat _rotation; - + glm::vec3 _hmdPosition; + glm::quat _hmdRotation; + glm::vec3 _targetPosition; + glm::quat _targetRotation; float _scale; }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 77009da18b..08fc5067fb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -246,10 +246,16 @@ Menu::Menu() : #endif addActionToQMenuAndActionHash(toolsMenu, - MenuOption::Console, - Qt::CTRL | Qt::ALT | Qt::Key_J, - this, - SLOT(toggleConsole())); + MenuOption::Console, + Qt::CTRL | Qt::ALT | Qt::Key_J, + this, + SLOT(toggleConsole())); + + addActionToQMenuAndActionHash(toolsMenu, + MenuOption::ResetSensors, + Qt::Key_Apostrophe, + appInstance, + SLOT(resetSensors())); QMenu* avatarMenu = addMenu("Avatar"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 67a4163c67..8fcce3dbf3 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -442,6 +442,7 @@ namespace MenuOption { const QString RenderLookAtVectors = "Show Look-at Vectors"; const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; const QString ResetAvatarSize = "Reset Avatar Size"; + const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 10a211f61b..24a1026ef4 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -328,6 +328,49 @@ bool MetavoxelSystem::findFirstRayHeightfieldIntersection(const glm::vec3& origi return true; } +class RayVoxelIntersectionVisitor : public RayIntersectionVisitor { +public: + + float intersectionDistance; + + RayVoxelIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info, float distance); +}; + +RayVoxelIntersectionVisitor::RayVoxelIntersectionVisitor(const glm::vec3& origin, + const glm::vec3& direction, const MetavoxelLOD& lod) : + RayIntersectionVisitor(origin, direction, QVector() << + Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), QVector(), lod), + intersectionDistance(FLT_MAX) { +} + +int RayVoxelIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { + if (!info.isLeaf) { + return _order; + } + const VoxelBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + glm::vec3 entry = ((_origin + distance * _direction) - info.minimum) / info.size; + if (buffer->findFirstRayIntersection(entry, _origin, _direction, intersectionDistance)) { + return SHORT_CIRCUIT; + } + return STOP_RECURSION; +} + +bool MetavoxelSystem::findFirstRayVoxelIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) { + RayVoxelIntersectionVisitor visitor(origin, direction, getLOD()); + guideToAugmented(visitor); + if (visitor.intersectionDistance == FLT_MAX) { + return false; + } + distance = visitor.intersectionDistance; + return true; +} + class HeightfieldHeightVisitor : public MetavoxelVisitor { public: @@ -411,10 +454,10 @@ float MetavoxelSystem::getHeightfieldHeight(const glm::vec3& location) { return visitor.height; } -class HeightfieldCursorRenderVisitor : public MetavoxelVisitor { +class CursorRenderVisitor : public MetavoxelVisitor { public: - HeightfieldCursorRenderVisitor(const Box& bounds); + CursorRenderVisitor(const AttributePointer& attribute, const Box& bounds); virtual int visit(MetavoxelInfo& info); @@ -423,13 +466,12 @@ private: Box _bounds; }; -HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const Box& bounds) : - MetavoxelVisitor(QVector() << - Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()), +CursorRenderVisitor::CursorRenderVisitor(const AttributePointer& attribute, const Box& bounds) : + MetavoxelVisitor(QVector() << attribute), _bounds(bounds) { } -int HeightfieldCursorRenderVisitor::visit(MetavoxelInfo& info) { +int CursorRenderVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_bounds)) { return STOP_RECURSION; } @@ -465,7 +507,8 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glActiveTexture(GL_TEXTURE0); glm::vec3 extents(radius, radius, radius); - HeightfieldCursorRenderVisitor visitor(Box(position - extents, position + extents)); + CursorRenderVisitor visitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), + Box(position - extents, position + extents)); guideToAugmented(visitor); DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().release(); @@ -478,6 +521,42 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glDepthFunc(GL_LESS); } +void MetavoxelSystem::renderVoxelCursor(const glm::vec3& position, float radius) { + glDepthFunc(GL_LEQUAL); + glEnable(GL_CULL_FACE); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + glEnableClientState(GL_VERTEX_ARRAY); + + DefaultMetavoxelRendererImplementation::getVoxelCursorProgram().bind(); + + glActiveTexture(GL_TEXTURE4); + float scale = 1.0f / radius; + glm::vec4 sCoefficients(scale, 0.0f, 0.0f, -scale * position.x); + glm::vec4 tCoefficients(0.0f, scale, 0.0f, -scale * position.y); + glm::vec4 rCoefficients(0.0f, 0.0f, scale, -scale * position.z); + glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&sCoefficients); + glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&tCoefficients); + glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&rCoefficients); + glActiveTexture(GL_TEXTURE0); + + glm::vec3 extents(radius, radius, radius); + CursorRenderVisitor visitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), + Box(position - extents, position + extents)); + guideToAugmented(visitor); + + DefaultMetavoxelRendererImplementation::getVoxelCursorProgram().release(); + + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_CULL_FACE); + glDepthFunc(GL_LESS); +} + void MetavoxelSystem::deleteTextures(int heightID, int colorID, int textureID) { glDeleteTextures(1, (GLuint*)&heightID); glDeleteTextures(1, (GLuint*)&colorID); @@ -991,10 +1070,12 @@ void VoxelPoint::setNormal(const glm::vec3& normal) { } VoxelBuffer::VoxelBuffer(const QVector& vertices, const QVector& indices, const QVector& hermite, - const QVector& materials) : + const QMultiHash& quadIndices, int size, const QVector& materials) : _vertices(vertices), _indices(indices), _hermite(hermite), + _quadIndices(quadIndices), + _size(size), _vertexCount(vertices.size()), _indexCount(indices.size()), _hermiteCount(hermite.size()), @@ -1002,18 +1083,107 @@ VoxelBuffer::VoxelBuffer(const QVector& vertices, const QVector _materials(materials) { } +static bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { + glm::vec3 firstSide = v0 - v1; + glm::vec3 secondSide = v2 - v1; + glm::vec3 normal = glm::cross(secondSide, firstSide); + float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); + if (dividend > 0.0f) { + return false; // origin below plane + } + float divisor = glm::dot(normal, direction); + if (divisor > -EPSILON) { + return false; + } + float t = dividend / divisor; + glm::vec3 point = origin + direction * t; + if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f && + glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f && + glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + distance = t; + return true; + } + return false; +} + +bool VoxelBuffer::findFirstRayIntersection(const glm::vec3& entry, const glm::vec3& origin, + const glm::vec3& direction, float& distance) const { + float highest = _size - 1.0f; + glm::vec3 position = entry * highest; + glm::vec3 floors = glm::floor(position); + int max = _size - 2; + int x = qMin((int)floors.x, max), y = qMin((int)floors.y, max), z = qMin((int)floors.z, max); + forever { + for (QMultiHash::const_iterator it = _quadIndices.constFind(qRgb(x + 1, y + 1, z + 1)); + it != _quadIndices.constEnd(); it++) { + const int* indices = _indices.constData() + *it; + if (findRayTriangleIntersection(origin, direction, _vertices.at(indices[0]).vertex, + _vertices.at(indices[1]).vertex, _vertices.at(indices[2]).vertex, distance) || + findRayTriangleIntersection(origin, direction, _vertices.at(indices[0]).vertex, + _vertices.at(indices[2]).vertex, _vertices.at(indices[3]).vertex, distance)) { + return true; + } + } + float xDistance = FLT_MAX, yDistance = FLT_MAX, zDistance = FLT_MAX; + if (direction.x > 0.0f) { + xDistance = (x + 1.0f - position.x) / direction.x; + } else if (direction.x < 0.0f) { + xDistance = (x - position.x) / direction.x; + } + if (direction.y > 0.0f) { + yDistance = (y + 1.0f - position.y) / direction.y; + } else if (direction.y < 0.0f) { + yDistance = (y - position.y) / direction.y; + } + if (direction.z > 0.0f) { + zDistance = (z + 1.0f - position.z) / direction.z; + } else if (direction.z < 0.0f) { + zDistance = (z - position.z) / direction.z; + } + float minimumDistance = qMin(xDistance, qMin(yDistance, zDistance)); + if (minimumDistance == xDistance) { + if (direction.x > 0.0f) { + if (x++ == max) { + return false; + } + } else if (x-- == 0) { + return false; + } + } + if (minimumDistance == yDistance) { + if (direction.y > 0.0f) { + if (y++ == max) { + return false; + } + } else if (y-- == 0) { + return false; + } + } + if (minimumDistance == zDistance) { + if (direction.z > 0.0f) { + if (z++ == max) { + return false; + } + } else if (z-- == 0) { + return false; + } + } + position += direction * minimumDistance; + } + return false; +} + void VoxelBuffer::render(bool cursor) { if (!_vertexBuffer.isCreated()) { _vertexBuffer.create(); _vertexBuffer.bind(); _vertexBuffer.allocate(_vertices.constData(), _vertices.size() * sizeof(VoxelPoint)); - _vertices.clear(); - + _indexBuffer.create(); _indexBuffer.bind(); _indexBuffer.allocate(_indices.constData(), _indices.size() * sizeof(int)); - _indices.clear(); - + if (!_materials.isEmpty()) { _networkTextures.resize(_materials.size()); for (int i = 0; i < _materials.size(); i++) { @@ -1036,7 +1206,7 @@ void VoxelBuffer::render(bool cursor) { glDrawRangeElements(GL_QUADS, 0, _vertexCount - 1, _indexCount, GL_UNSIGNED_INT, 0); - if (!_materials.isEmpty()) { + if (!(_materials.isEmpty() || cursor)) { Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true, false); glDepthFunc(GL_LEQUAL); @@ -1118,7 +1288,7 @@ void VoxelBuffer::render(bool cursor) { _vertexBuffer.release(); _indexBuffer.release(); - if (_hermiteCount > 0 && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData)) { + if (_hermiteCount > 0 && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData) && !cursor) { if (!_hermiteBuffer.isCreated()) { _hermiteBuffer.create(); _hermiteBuffer.bind(); @@ -1214,6 +1384,12 @@ void DefaultMetavoxelRendererImplementation::init() { _baseVoxelProgram.link(); loadSplatProgram("voxel", _splatVoxelProgram, _splatVoxelLocations); + + _voxelCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_voxel_cursor.vert"); + _voxelCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_voxel_cursor.frag"); + _voxelCursorProgram.link(); } } @@ -1627,6 +1803,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { QVector vertices; QVector indices; QVector hermiteSegments; + QMultiHash quadIndices; // see http://www.frankpetterson.com/publications/dualcontour/dualcontour.pdf for a description of the // dual contour algorithm for generating meshes from voxel data using Hermite-tagged edges @@ -2091,6 +2268,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { // quads for each edge that includes a transition, using indices of previously generated vertices if (x != 0 && y != 0 && z != 0) { if (alpha0 != alpha1) { + quadIndices.insert(qRgb(x, y, z), indices.size()); + quadIndices.insert(qRgb(x, y - 1, z), indices.size()); + quadIndices.insert(qRgb(x, y - 1, z - 1), indices.size()); + quadIndices.insert(qRgb(x, y, z - 1), indices.size()); indices.append(index.x); int index1 = lastLineIndices.at(x).x; int index2 = lastPlaneIndices.at((y - 1) * expanded + x).x; @@ -2107,6 +2288,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } if (alpha0 != alpha2) { + quadIndices.insert(qRgb(x, y, z), indices.size()); + quadIndices.insert(qRgb(x - 1, y, z), indices.size()); + quadIndices.insert(qRgb(x - 1, y, z - 1), indices.size()); + quadIndices.insert(qRgb(x, y, z - 1), indices.size()); indices.append(index.y); int index1 = lastIndex.y; int index2 = lastPlaneIndices.at(y * expanded + x - 1).y; @@ -2123,6 +2308,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { } if (alpha0 != alpha4) { + quadIndices.insert(qRgb(x, y, z), indices.size()); + quadIndices.insert(qRgb(x - 1, y, z), indices.size()); + quadIndices.insert(qRgb(x - 1, y - 1, z), indices.size()); + quadIndices.insert(qRgb(x, y - 1, z), indices.size()); indices.append(index.z); int index1 = lastIndex.z; int index2 = lastLineIndices.at(x - 1).z; @@ -2158,7 +2347,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { colorZ += area; } } - buffer = new VoxelBuffer(vertices, indices, hermiteSegments, + buffer = new VoxelBuffer(vertices, indices, hermiteSegments, quadIndices, size, material ? material->getMaterials() : QVector()); } BufferDataPointer pointer(buffer); @@ -2430,6 +2619,7 @@ ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram; ProgramObject DefaultMetavoxelRendererImplementation::_baseVoxelProgram; ProgramObject DefaultMetavoxelRendererImplementation::_splatVoxelProgram; DefaultMetavoxelRendererImplementation::SplatLocations DefaultMetavoxelRendererImplementation::_splatVoxelLocations; +ProgramObject DefaultMetavoxelRendererImplementation::_voxelCursorProgram; static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { GLdouble coefficients[] = { x, y, z, w }; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 52706aab56..f479ba5e50 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -46,8 +46,12 @@ public: void renderHeightfieldCursor(const glm::vec3& position, float radius); + void renderVoxelCursor(const glm::vec3& position, float radius); + bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); + bool findFirstRayVoxelIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); + Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); Q_INVOKABLE void deleteTextures(int heightID, int colorID, int textureID); @@ -241,7 +245,13 @@ class VoxelBuffer : public BufferData { public: VoxelBuffer(const QVector& vertices, const QVector& indices, const QVector& hermite, - const QVector& materials = QVector()); + const QMultiHash& quadIndices, int size, const QVector& materials = + QVector()); + + /// Finds the first intersection between the described ray and the voxel data. + /// \param entry the entry point of the ray in relative coordinates, from (0, 0, 0) to (1, 1, 1) + bool findFirstRayIntersection(const glm::vec3& entry, const glm::vec3& origin, + const glm::vec3& direction, float& distance) const; virtual void render(bool cursor = false); @@ -250,6 +260,8 @@ private: QVector _vertices; QVector _indices; QVector _hermite; + QMultiHash _quadIndices; + int _size; int _vertexCount; int _indexCount; int _hermiteCount; @@ -311,6 +323,8 @@ public: static ProgramObject& getSplatVoxelProgram() { return _splatVoxelProgram; } static const SplatLocations& getSplatVoxelLocations() { return _splatVoxelLocations; } + static ProgramObject& getVoxelCursorProgram() { return _voxelCursorProgram; } + Q_INVOKABLE DefaultMetavoxelRendererImplementation(); virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod); @@ -344,6 +358,8 @@ private: static ProgramObject _baseVoxelProgram; static ProgramObject _splatVoxelProgram; static SplatLocations _splatVoxelLocations; + + static ProgramObject _voxelCursorProgram; }; /// Base class for spanner renderers; provides clipping. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c0ce474d16..36035880fd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1151,7 +1151,7 @@ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { const Head* head = getHead(); - return (renderMode != NORMAL_RENDER_MODE) || + return (renderMode != NORMAL_RENDER_MODE) || (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) || (glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); } diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp deleted file mode 100644 index bc2c4bb2d1..0000000000 --- a/interface/src/devices/CaraFaceTracker.cpp +++ /dev/null @@ -1,455 +0,0 @@ -// -// CaraFaceTracker.cpp -// interface/src/devices -// -// Created by Li Zuwei on 7/22/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "CaraFaceTracker.h" -#include - -//qt -#include -#include -#include -#include - -#define PI M_PI -#define RADTODEG(x) ( (x) * 180.0 / PI ) -#define DEGTORAD(x) ( (x) * PI / 180.0 ) - -static const QHostAddress CARA_FEATURE_POINT_SERVER_ADDR("127.0.0.1"); -static const quint16 CARA_FEATURE_POINT_SERVER_PORT = 36555; -static QString sampleJson = "[{\"id\":1, \ - \"face\":{\"x\":248,\"y\":64,\"width\":278,\"height\":341}, \ - \"pose\":{\"roll\":2.62934,\"pitch\":-12.2318,\"yaw\":0.936743}, \ - \"feature_points\":[314,194,326,187,340,187,354,189,367,193,409,190,421,187,435,184,448,183,459,188, \ - 388,207,389,223,390,240,391,257,377,266,384,267,392,268,399,266,407,264,331,209, \ - 341,204,354,204,364,209,353,214,341,214,410,208,420,201,433,200,443,205,434,211, \ - 421,211,362,294,372,290,383,287,393,289,404,286,415,289,426,291,418,300,407,306, \ - 394,308,382,307,371,302,383,295,394,295,404,294,404,295,393,297,383,296], \ - \"classifiers\":{\"emotion\":{\"smi\":-0.368829,\"sur\":-1.33334,\"neg\":0.00235828,\"att\":1},\"blink\":1}}]"; - -static const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.0f); -static const float TRANSLATION_SCALE = 1.0f; -static const int NUM_BLENDSHAPE_COEFF = 30; -static const int NUM_SMOOTHING_SAMPLES = 3; - -struct CaraPerson { - struct CaraPose { - float roll, pitch, yaw; - CaraPose() : - roll(0.0f), - pitch(0.0f), - yaw(0.0f) - { - } - }; - - struct CaraEmotion { - float smile, surprise, negative, attention; - CaraEmotion(): - smile(0.0f), - surprise(0.0f), - negative(0.0f), - attention(0.0f) - { - } - }; - - enum CaraBlink { - BLINK_NOT_AVAILABLE, - NO_BLINK, - BLINK - }; - - CaraPerson() : - id(-1), - blink(BLINK_NOT_AVAILABLE) - { - } - - int id; - CaraPose pose; - CaraEmotion emotion; - CaraBlink blink; - - QString toString() { - QString s = QString("id: %1, roll: %2, pitch: %3, yaw: %4, smi: %5, sur: %6, neg: %7, att: %8, blink: %9"). - arg(id). - arg(pose.roll). - arg(pose.pitch). - arg(pose.yaw). - arg(emotion.smile). - arg(emotion.surprise). - arg(emotion.negative). - arg(emotion.attention). - arg(blink); - return s; - } -}; - -class CaraPacketDecoder { -public: - static CaraPerson extractOne(const QByteArray& buffer, QJsonParseError* jsonError) { - CaraPerson person; - QJsonDocument dom = QJsonDocument::fromJson(buffer, jsonError); - - //check for errors - if(jsonError->error == QJsonParseError::NoError) { - //read the dom structure and populate the blend shapes and head poses - //qDebug() << "[Info] Cara Face Tracker Packet Parsing Successful!"; - - //begin extracting the packet - if(dom.isArray()) { - QJsonArray people = dom.array(); - //extract the first person in the array - if(people.size() > 0) { - QJsonValue val = people.at(0); - if(val.isObject()) { - QJsonObject personDOM = val.toObject(); - person.id = extractId(personDOM); - person.pose = extractPose(personDOM); - - //extract the classifier outputs - QJsonObject::const_iterator it = personDOM.constFind("classifiers"); - if(it != personDOM.constEnd()) { - QJsonObject classifierDOM = (*it).toObject(); - person.emotion = extractEmotion(classifierDOM); - person.blink = extractBlink(classifierDOM); - } - } - } - } - } - - return person; - } - -private: - static int extractId(const QJsonObject& person) { - int id = -1; - QJsonObject::const_iterator it = person.constFind("id"); - if(it != person.constEnd()) { - id = (*it).toInt(-1); - } - return id; - } - - static CaraPerson::CaraPose extractPose(const QJsonObject& person) { - CaraPerson::CaraPose pose; - QJsonObject::const_iterator it = person.constFind("pose"); - if(it != person.constEnd()) { - QJsonObject poseDOM = (*it).toObject(); - - //look for the roll, pitch, yaw; - QJsonObject::const_iterator poseIt = poseDOM.constFind("roll"); - QJsonObject::const_iterator poseEnd = poseDOM.constEnd(); - if(poseIt != poseEnd) { - pose.roll = (float)(*poseIt).toDouble(0.0); - } - poseIt = poseDOM.constFind("pitch"); - if(poseIt != poseEnd) { - pose.pitch = (float)(*poseIt).toDouble(0.0); - } - poseIt = poseDOM.constFind("yaw"); - if(poseIt != poseEnd) { - pose.yaw = (float)(*poseIt).toDouble(0.0); - } - } - return pose; - } - - static CaraPerson::CaraEmotion extractEmotion(const QJsonObject& classifiers) { - CaraPerson::CaraEmotion emotion; - QJsonObject::const_iterator it = classifiers.constFind("emotion"); - if(it != classifiers.constEnd()) { - QJsonObject emotionDOM = (*it).toObject(); - - //look for smile, surprise, negative, attention responses - QJsonObject::const_iterator emoEnd = emotionDOM.constEnd(); - QJsonObject::const_iterator emoIt = emotionDOM.constFind("smi"); - if(emoIt != emoEnd) { - emotion.smile = (float)(*emoIt).toDouble(0.0); - } - emoIt = emotionDOM.constFind("sur"); - if(emoIt != emoEnd) { - emotion.surprise = (float)(*emoIt).toDouble(0.0); - } - emoIt = emotionDOM.constFind("neg"); - if(emoIt != emoEnd) { - emotion.negative = (float)(*emoIt).toDouble(0.0); - } - emoIt = emotionDOM.constFind("att"); - if(emoIt != emoEnd) { - emotion.attention = (float)(*emoIt).toDouble(0.0); - } - } - return emotion; - } - - static CaraPerson::CaraBlink extractBlink(const QJsonObject& classifiers) { - CaraPerson::CaraBlink blink = CaraPerson::BLINK_NOT_AVAILABLE; - QJsonObject::const_iterator it = classifiers.constFind("blink"); - if(it != classifiers.constEnd()) { - int b = (*it).toInt(CaraPerson::BLINK_NOT_AVAILABLE); - switch(b) { - case CaraPerson::BLINK_NOT_AVAILABLE: - blink = CaraPerson::BLINK_NOT_AVAILABLE; - break; - case CaraPerson::NO_BLINK: - blink = CaraPerson::NO_BLINK; - break; - case CaraPerson::BLINK: - blink = CaraPerson::BLINK; - break; - default: - blink = CaraPerson::BLINK_NOT_AVAILABLE; - break; - } - } - return blink; - } -}; - -CaraFaceTracker::CaraFaceTracker() : - _lastReceiveTimestamp(0), - _pitchAverage(NUM_SMOOTHING_SAMPLES), - _yawAverage(NUM_SMOOTHING_SAMPLES), - _rollAverage(NUM_SMOOTHING_SAMPLES), - _eyeGazeLeftPitch(0.0f), - _eyeGazeLeftYaw(0.0f), - _eyeGazeRightPitch(0.0f), - _eyeGazeRightYaw(0), - _leftBlinkIndex(0), - _rightBlinkIndex(1), - _leftEyeOpenIndex(8), - _rightEyeOpenIndex(9), - _browDownLeftIndex(14), - _browDownRightIndex(15), - _browUpCenterIndex(16), - _browUpLeftIndex(17), - _browUpRightIndex(18), - _mouthSmileLeftIndex(28), - _mouthSmileRightIndex(29), - _jawOpenIndex(21) -{ - connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); - connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState))); - - bindTo(CARA_FEATURE_POINT_SERVER_PORT); - - _headTranslation = DEFAULT_HEAD_ORIGIN; - _blendshapeCoefficients.resize(NUM_BLENDSHAPE_COEFF); - _blendshapeCoefficients.fill(0.0f); - - //qDebug() << sampleJson; -} - -CaraFaceTracker::CaraFaceTracker(const QHostAddress& host, quint16 port) : - _lastReceiveTimestamp(0), - _pitchAverage(NUM_SMOOTHING_SAMPLES), - _yawAverage(NUM_SMOOTHING_SAMPLES), - _rollAverage(NUM_SMOOTHING_SAMPLES), - _eyeGazeLeftPitch(0.0f), - _eyeGazeLeftYaw(0.0f), - _eyeGazeRightPitch(0.0f), - _eyeGazeRightYaw(0.0f), - _leftBlinkIndex(0), - _rightBlinkIndex(1), - _leftEyeOpenIndex(8), - _rightEyeOpenIndex(9), - _browDownLeftIndex(14), - _browDownRightIndex(15), - _browUpCenterIndex(16), - _browUpLeftIndex(17), - _browUpRightIndex(18), - _mouthSmileLeftIndex(28), - _mouthSmileRightIndex(29), - _jawOpenIndex(21) -{ - connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); - connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(socketStateChanged(QAbstractSocket::SocketState))); - - bindTo(host, port); - - _headTranslation = DEFAULT_HEAD_ORIGIN * TRANSLATION_SCALE; - _blendshapeCoefficients.resize(NUM_BLENDSHAPE_COEFF); //set the size of the blendshape coefficients - _blendshapeCoefficients.fill(0.0f); -} - -CaraFaceTracker::~CaraFaceTracker() { - if(_udpSocket.isOpen()) - _udpSocket.close(); -} - -void CaraFaceTracker::init() { - -} - -void CaraFaceTracker::reset() { - -} - -void CaraFaceTracker::bindTo(quint16 port) { - bindTo(QHostAddress::Any, port); -} - -void CaraFaceTracker::bindTo(const QHostAddress& host, quint16 port) { - if(_udpSocket.isOpen()) { - _udpSocket.close(); - } - _udpSocket.bind(host, port); -} - -bool CaraFaceTracker::isActive() const { - static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs - return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS); -} - -void CaraFaceTracker::update() { - // get the euler angles relative to the window - glm::vec3 eulers = glm::degrees(safeEulerAngles(_headRotation * glm::quat(glm::radians(glm::vec3( - (_eyeGazeLeftPitch + _eyeGazeRightPitch) / 2.0f, (_eyeGazeLeftYaw + _eyeGazeRightYaw) / 2.0f, 0.0f))))); - - //TODO: integrate when cara has eye gaze estimation - - _estimatedEyePitch = eulers.x; - _estimatedEyeYaw = eulers.y; -} - -//private slots and methods -void CaraFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) { - qDebug() << "[Error] Cara Face Tracker Socket Error: " << _udpSocket.errorString(); -} - -void CaraFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) { - QString state; - switch(socketState) { - case QAbstractSocket::BoundState: - state = "Bounded"; - break; - case QAbstractSocket::ClosingState: - state = "Closing"; - break; - case QAbstractSocket::ConnectedState: - state = "Connected"; - break; - case QAbstractSocket::ConnectingState: - state = "Connecting"; - break; - case QAbstractSocket::HostLookupState: - state = "Host Lookup"; - break; - case QAbstractSocket::ListeningState: - state = "Listening"; - break; - case QAbstractSocket::UnconnectedState: - state = "Unconnected"; - break; - } - qDebug() << "[Info] Cara Face Tracker Socket: " << socketState; -} - -void CaraFaceTracker::readPendingDatagrams() { - QByteArray buffer; - while (_udpSocket.hasPendingDatagrams()) { - buffer.resize(_udpSocket.pendingDatagramSize()); - _udpSocket.readDatagram(buffer.data(), buffer.size()); - decodePacket(buffer); - } -} - -void CaraFaceTracker::decodePacket(const QByteArray& buffer) { - //decode the incoming udp packet - QJsonParseError jsonError; - CaraPerson person = CaraPacketDecoder::extractOne(buffer, &jsonError); - - if(jsonError.error == QJsonParseError::NoError) { - - //do some noise filtering to the head poses - //reduce the noise first by truncating to 1 dp - person.pose.roll = glm::floor(person.pose.roll * 10) / 10; - person.pose.pitch = glm::floor(person.pose.pitch * 10) / 10; - person.pose.yaw = glm::floor(person.pose.yaw * 10) / 10; - - //qDebug() << person.toString(); - - glm::quat newRotation(glm::vec3(DEGTORAD(person.pose.pitch), DEGTORAD(person.pose.yaw), DEGTORAD(person.pose.roll))); - - // Compute angular velocity of the head - glm::quat r = newRotation * glm::inverse(_headRotation); - float theta = 2 * acos(r.w); - if (theta > EPSILON) { - float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - const float AVERAGE_CARA_FRAME_TIME = 0.04f; - const float YAW_STANDARD_DEV_DEG = 2.5f; - - _headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag; - _pitchAverage.updateAverage(person.pose.pitch); - _rollAverage.updateAverage(person.pose.roll); - - //could use the angular velocity to detemine whether to update pitch and roll to further remove the noise. - //use the angular velocity for roll and pitch, update if > THRESHOLD - //if(glm::abs(_headAngularVelocity.x) > ANGULAR_VELOCITY_MIN) { - // _pitchAverage.updateAverage(person.pose.pitch); - //} - - //if(glm::abs(_headAngularVelocity.z) > ANGULAR_VELOCITY_MIN) { - // _rollAverage.updateAverage(person.pose.roll);; - //} - - //for yaw, the jitter is great, you can't use angular velocity because it swings too much - //use the previous and current yaw, calculate the - //abs difference and move it the difference is above the standard deviation which is around 2.5 - // > the standard deviation 2.5 deg, update the yaw smoothing average - if(glm::abs(person.pose.yaw - _yawAverage.getAverage()) > YAW_STANDARD_DEV_DEG) { - //qDebug() << "Yaw Diff: " << glm::abs(person.pose.yaw - _previousYaw); - _yawAverage.updateAverage(person.pose.yaw); - } - - //set the new rotation - newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); - } - else { - //no change in position, use previous averages - newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); - _headAngularVelocity = glm::vec3(0,0,0); - } - - //update to new rotation angles - _headRotation = newRotation; - - //TODO: head translation, right now is 0 - - //Do Blendshapes, clip between 0.0f to 1.0f, neg should be ignored - _blendshapeCoefficients[_leftBlinkIndex] = person.blink == CaraPerson::BLINK ? 1.0f : 0.0f; - _blendshapeCoefficients[_rightBlinkIndex] = person.blink == CaraPerson::BLINK ? 1.0f : 0.0f; - - //anger and surprised are mutually exclusive so we could try use this fact to determine - //whether to down the brows or up the brows - _blendshapeCoefficients[_browDownLeftIndex] = person.emotion.negative < 0.0f ? 0.0f : person.emotion.negative; - _blendshapeCoefficients[_browDownRightIndex] = person.emotion.negative < 0.0f ? 0.0f : person.emotion.negative; - _blendshapeCoefficients[_browUpCenterIndex] = person.emotion.surprise < 0.0f ? 0.0f : person.emotion.surprise; - _blendshapeCoefficients[_browUpLeftIndex] = person.emotion.surprise < 0.0f ? 0.0f : person.emotion.surprise; - _blendshapeCoefficients[_browUpRightIndex] = person.emotion.surprise < 0.0f ? 0.0f : person.emotion.surprise; - _blendshapeCoefficients[_jawOpenIndex] = person.emotion.surprise < 0.0f ? 0.0f : person.emotion.surprise; - _blendshapeCoefficients[_mouthSmileLeftIndex] = person.emotion.smile < 0.0f ? 0.0f : person.emotion.smile; - _blendshapeCoefficients[_mouthSmileRightIndex] = person.emotion.smile < 0.0f ? 0.0f : person.emotion.smile; - } - else { - qDebug() << "[Error] Cara Face Tracker Decode Error: " << jsonError.errorString(); - } - - _lastReceiveTimestamp = usecTimestampNow(); -} - -float CaraFaceTracker::getBlendshapeCoefficient(int index) const { - return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f; -} diff --git a/interface/src/devices/CaraFaceTracker.h b/interface/src/devices/CaraFaceTracker.h deleted file mode 100644 index 0c715afda9..0000000000 --- a/interface/src/devices/CaraFaceTracker.h +++ /dev/null @@ -1,124 +0,0 @@ -// -// CaraFaceTracker.h -// interface/src/devices -// -// Created by Li Zuwei on 7/22/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_CaraFaceTracker_h -#define hifi_CaraFaceTracker_h - -#include - -#include -#include "FaceTracker.h" - -/*! - * \class CaraFaceTracker - * - * \brief Handles interaction with the Cara software, - * which provides head position/orientation and facial features. - * \details By default, opens a udp socket with IPV4_ANY_ADDR with port 36555. - * User needs to run the Cara Face Detection UDP Client with the destination - * host address (eg: 127.0.0.1 for localhost) and destination port 36555. -**/ - -class CaraFaceTracker : public FaceTracker { - Q_OBJECT - -public: - CaraFaceTracker(); - CaraFaceTracker(const QHostAddress& host, quint16 port); - ~CaraFaceTracker(); - - //initialization - void init(); - void reset(); - - //sockets - void bindTo(quint16 port); - void bindTo(const QHostAddress& host, quint16 port); - bool isActive() const; - - //tracking - void update(); - - //head angular velocity - const glm::vec3& getHeadAngularVelocity() const { return _headAngularVelocity; } - - //eye gaze - float getEyeGazeLeftPitch() const { return _eyeGazeLeftPitch; } - float getEyeGazeLeftYaw() const { return _eyeGazeLeftYaw; } - - float getEyeGazeRightPitch() const { return _eyeGazeRightPitch; } - float getEyeGazeRightYaw() const { return _eyeGazeRightYaw; } - - //blend shapes - float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); } - float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); } - float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); } - float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); } - - float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); } - float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); } - float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); } - float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); } - float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); } - - float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); } - float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); } - float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); } - -private slots: - - //sockets - void socketErrorOccurred(QAbstractSocket::SocketError socketError); - void readPendingDatagrams(); - void socketStateChanged(QAbstractSocket::SocketState socketState); - -private: - void decodePacket(const QByteArray& buffer); - float getBlendshapeCoefficient(int index) const; - - // sockets - QUdpSocket _udpSocket; - quint64 _lastReceiveTimestamp; - - //head tracking - glm::vec3 _headAngularVelocity; - - //pose average - SimpleMovingAverage _pitchAverage; - SimpleMovingAverage _yawAverage; - SimpleMovingAverage _rollAverage; - - // eye gaze degrees - float _eyeGazeLeftPitch; - float _eyeGazeLeftYaw; - float _eyeGazeRightPitch; - float _eyeGazeRightYaw; - - //blend shapes - int _leftBlinkIndex; - int _rightBlinkIndex; - int _leftEyeOpenIndex; - int _rightEyeOpenIndex; - - // Brows - int _browDownLeftIndex; - int _browDownRightIndex; - int _browUpCenterIndex; - int _browUpLeftIndex; - int _browUpRightIndex; - - int _mouthSmileLeftIndex; - int _mouthSmileRightIndex; - - int _jawOpenIndex; -}; - -#endif //endif hifi_CaraFaceTracker_h diff --git a/interface/src/devices/Joystick.cpp b/interface/src/devices/Joystick.cpp index fe67eaceaa..e110027e1a 100644 --- a/interface/src/devices/Joystick.cpp +++ b/interface/src/devices/Joystick.cpp @@ -15,13 +15,17 @@ #include "Joystick.h" -#ifdef HAVE_SDL +const float MAX_AXIS = 32768.0f; -Joystick::Joystick(const QString& name, SDL_Joystick* sdlJoystick) : +#ifdef HAVE_SDL2 + +Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) : + _instanceId(instanceId), _name(name), - _axes(QVector(SDL_JoystickNumAxes(sdlJoystick))), - _buttons(QVector(SDL_JoystickNumButtons(sdlJoystick))), - _sdlJoystick(sdlJoystick) + _sdlGameController(sdlGameController), + _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), + _axes(QVector(SDL_JoystickNumAxes(_sdlJoystick))), + _buttons(QVector(SDL_JoystickNumButtons(_sdlJoystick))) { } @@ -33,30 +37,24 @@ Joystick::~Joystick() { } void Joystick::closeJoystick() { -#ifdef HAVE_SDL - SDL_JoystickClose(_sdlJoystick); +#ifdef HAVE_SDL2 + SDL_GameControllerClose(_sdlGameController); #endif } -void Joystick::update() { -#ifdef HAVE_SDL - // update our current values, emit a signal when there is a change - for (int j = 0; j < getNumAxes(); j++) { - float newValue = glm::round(SDL_JoystickGetAxis(_sdlJoystick, j) + 0.5f) / std::numeric_limits::max(); - if (_axes[j] != newValue) { - float oldValue = _axes[j]; - _axes[j] = newValue; - emit axisValueChanged(j, newValue, oldValue); - } - } - for (int j = 0; j < getNumButtons(); j++) { - bool newValue = SDL_JoystickGetButton(_sdlJoystick, j); - if (_buttons[j] != newValue) { - bool oldValue = _buttons[j]; - _buttons[j] = newValue; - emit buttonStateChanged(j, newValue, oldValue); - } - } +#ifdef HAVE_SDL2 +void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { + float oldValue = _axes[event.axis]; + float newValue = event.value / MAX_AXIS; + _axes[event.axis] = newValue; + emit axisValueChanged(event.axis, newValue, oldValue); +} + +void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { + bool oldValue = _buttons[event.button]; + bool newValue = event.state == SDL_PRESSED; + _buttons[event.button] = newValue; + emit buttonStateChanged(event.button, newValue, oldValue); +} #endif -} \ No newline at end of file diff --git a/interface/src/devices/Joystick.h b/interface/src/devices/Joystick.h index 8343c20a04..eeeeb03759 100644 --- a/interface/src/devices/Joystick.h +++ b/interface/src/devices/Joystick.h @@ -15,7 +15,7 @@ #include #include -#ifdef HAVE_SDL +#ifdef HAVE_SDL2 #include #undef main #endif @@ -24,6 +24,10 @@ class Joystick : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) + +#ifdef HAVE_SDL2 + Q_PROPERTY(int instanceId READ getInstanceId) +#endif Q_PROPERTY(int numAxes READ getNumAxes) Q_PROPERTY(int numButtons READ getNumButtons) @@ -31,19 +35,21 @@ public: Joystick(); ~Joystick(); -#ifdef HAVE_SDL - Joystick(const QString& name, SDL_Joystick* sdlJoystick); +#ifdef HAVE_SDL2 + Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController); #endif - void update(); - void closeJoystick(); - -#ifdef HAVE_SDL - void setSDLJoystick(SDL_Joystick* sdlJoystick) { _sdlJoystick = sdlJoystick; } + +#ifdef HAVE_SDL2 + void handleAxisEvent(const SDL_ControllerAxisEvent& event); + void handleButtonEvent(const SDL_ControllerButtonEvent& event); #endif const QString& getName() const { return _name; } +#ifdef HAVE_SDL2 + int getInstanceId() const { return _instanceId; } +#endif const QVector& getAxes() const { return _axes; } const QVector& getButtons() const { return _buttons; } @@ -55,13 +61,15 @@ signals: void axisValueChanged(int axis, float newValue, float oldValue); void buttonStateChanged(int button, float newValue, float oldValue); private: +#ifdef HAVE_SDL2 + SDL_GameController* _sdlGameController; + SDL_Joystick* _sdlJoystick; + SDL_JoystickID _instanceId; +#endif + QString _name; QVector _axes; QVector _buttons; - -#ifdef HAVE_SDL - SDL_Joystick* _sdlJoystick; -#endif }; -#endif // hifi_JoystickTracker_h \ No newline at end of file +#endif // hifi_JoystickTracker_h diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index eb48e3d463..62894510c2 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -55,6 +55,20 @@ bool OculusManager::_programInitialized = false; Camera* OculusManager::_camera = NULL; int OculusManager::_activeEyeIndex = -1; +float OculusManager::CALIBRATION_DELTA_MINIMUM_LENGTH = 0.02f; +float OculusManager::CALIBRATION_DELTA_MINIMUM_ANGLE = 5.f * RADIANS_PER_DEGREE; +float OculusManager::CALIBRATION_ZERO_MAXIMUM_LENGTH = 0.01f; +float OculusManager::CALIBRATION_ZERO_MAXIMUM_ANGLE = 2.0f * RADIANS_PER_DEGREE; +quint64 OculusManager::CALIBRATION_ZERO_HOLD_TIME = 3000000; // usec +float OculusManager::CALIBRATION_MESSAGE_DISTANCE = 2.5f; +OculusManager::CalibrationState OculusManager::_calibrationState; +glm::vec3 OculusManager::_calibrationPosition; +glm::quat OculusManager::_calibrationOrientation; +quint64 OculusManager::_calibrationStartTime; +int OculusManager::_calibrationMessage = NULL; +QString OculusManager::CALIBRATION_BILLBOARD_URL = "http://hifi-public.s3.amazonaws.com/images/hold-to-calibrate.svg"; +float OculusManager::CALIBRATION_BILLBOARD_SCALE = 2.f; + #endif glm::vec3 OculusManager::_leftEyePosition = glm::vec3(); @@ -62,6 +76,10 @@ glm::vec3 OculusManager::_rightEyePosition = glm::vec3(); void OculusManager::connect() { #ifdef HAVE_LIBOVR + _calibrationState = UNCALIBRATED; + + qDebug() << "Oculus SDK" << OVR_VERSION_STRING; + ovr_Initialize(); _ovrHmd = ovrHmd_Create(0); @@ -172,6 +190,124 @@ void OculusManager::disconnect() { #endif } +#ifdef HAVE_LIBOVR +void OculusManager::positionCalibrationBillboard(BillboardOverlay* billboard) { + glm::quat headOrientation = Application::getInstance()->getAvatar()->getHeadOrientation(); + headOrientation.x = 0; + headOrientation.z = 0; + glm::normalize(headOrientation); + billboard->setPosition(Application::getInstance()->getAvatar()->getHeadPosition() + + headOrientation * glm::vec3(0.f, 0.f, -CALIBRATION_MESSAGE_DISTANCE)); + billboard->setRotation(headOrientation); +} +#endif + +#ifdef HAVE_LIBOVR +void OculusManager::calibrate(glm::vec3 position, glm::quat orientation) { + static QString progressMessage; + static BillboardOverlay* billboard; + + switch (_calibrationState) { + + case UNCALIBRATED: + if (position != glm::vec3() && orientation != glm::quat()) { // Handle zero values at start-up. + _calibrationPosition = position; + _calibrationOrientation = orientation; + _calibrationState = WAITING_FOR_DELTA; + } + break; + + case WAITING_FOR_DELTA: + if (glm::length(position - _calibrationPosition) > CALIBRATION_DELTA_MINIMUM_LENGTH + || glm::angle(orientation * glm::inverse(_calibrationOrientation)) > CALIBRATION_DELTA_MINIMUM_ANGLE) { + _calibrationPosition = position; + _calibrationOrientation = orientation; + _calibrationState = WAITING_FOR_ZERO; + } + break; + + case WAITING_FOR_ZERO: + if (glm::length(position - _calibrationPosition) < CALIBRATION_ZERO_MAXIMUM_LENGTH + && glm::angle(orientation * glm::inverse(_calibrationOrientation)) < CALIBRATION_ZERO_MAXIMUM_ANGLE) { + _calibrationStartTime = usecTimestampNow(); + _calibrationState = WAITING_FOR_ZERO_HELD; + + if (!_calibrationMessage) { + qDebug() << "Hold still to calibrate HMD"; + + billboard = new BillboardOverlay(); + billboard->setURL(CALIBRATION_BILLBOARD_URL); + billboard->setScale(CALIBRATION_BILLBOARD_SCALE); + billboard->setIsFacingAvatar(false); + positionCalibrationBillboard(billboard); + + _calibrationMessage = Application::getInstance()->getOverlays().addOverlay(billboard); + } + + progressMessage = ""; + } else { + _calibrationPosition = position; + _calibrationOrientation = orientation; + } + break; + + case WAITING_FOR_ZERO_HELD: + if (glm::length(position - _calibrationPosition) < CALIBRATION_ZERO_MAXIMUM_LENGTH + && glm::angle(orientation * glm::inverse(_calibrationOrientation)) < CALIBRATION_ZERO_MAXIMUM_ANGLE) { + if ((usecTimestampNow() - _calibrationStartTime) > CALIBRATION_ZERO_HOLD_TIME) { + _calibrationState = CALIBRATED; + qDebug() << "HMD calibrated"; + Application::getInstance()->getOverlays().deleteOverlay(_calibrationMessage); + _calibrationMessage = NULL; + Application::getInstance()->resetSensors(); + } else { + quint64 quarterSeconds = (usecTimestampNow() - _calibrationStartTime) / 250000; + if (quarterSeconds + 1 > progressMessage.length()) { + // 3...2...1... + if (quarterSeconds == 4 * (quarterSeconds / 4)) { + quint64 wholeSeconds = CALIBRATION_ZERO_HOLD_TIME / 1000000 - quarterSeconds / 4; + + if (wholeSeconds == 3) { + positionCalibrationBillboard(billboard); + } + + progressMessage += QString::number(wholeSeconds); + } else { + progressMessage += "."; + } + //qDebug() << progressMessage; // Progress message ready for 3D text overlays. + } + } + } else { + _calibrationPosition = position; + _calibrationOrientation = orientation; + _calibrationState = WAITING_FOR_ZERO; + } + break; + default: + break; + + } +} +#endif + +void OculusManager::recalibrate() { +#ifdef HAVE_LIBOVR + _calibrationState = UNCALIBRATED; +#endif +} + +void OculusManager::abandonCalibration() { +#ifdef HAVE_LIBOVR + _calibrationState = CALIBRATED; + if (_calibrationMessage) { + qDebug() << "Abandoned HMD calibration"; + Application::getInstance()->getOverlays().deleteOverlay(_calibrationMessage); + _calibrationMessage = NULL; + } +#endif +} + #ifdef HAVE_LIBOVR void OculusManager::generateDistortionMesh() { @@ -325,6 +461,13 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p ovrVector3f ovrHeadPosition = ts.HeadPose.ThePose.Position; trackerPosition = glm::vec3(ovrHeadPosition.x, ovrHeadPosition.y, ovrHeadPosition.z); + + if (_calibrationState != CALIBRATED) { + ovrQuatf ovrHeadOrientation = ts.HeadPose.ThePose.Orientation; + orientation = glm::quat(ovrHeadOrientation.w, ovrHeadOrientation.x, ovrHeadOrientation.y, ovrHeadOrientation.z); + calibrate(trackerPosition, orientation); + } + trackerPosition = bodyOrientation * trackerPosition; #endif @@ -337,15 +480,20 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p #else ovrEyeType eye = _ovrHmdDesc.EyeRenderOrder[eyeIndex]; #endif - //Set the camera rotation for this eye + // Set the camera rotation for this eye eyeRenderPose[eye] = ovrHmd_GetEyePose(_ovrHmd, eye); orientation.x = eyeRenderPose[eye].Orientation.x; orientation.y = eyeRenderPose[eye].Orientation.y; orientation.z = eyeRenderPose[eye].Orientation.z; orientation.w = eyeRenderPose[eye].Orientation.w; - _camera->setRotation(bodyOrientation * orientation); - _camera->setPosition(position + trackerPosition); + // Update the application camera with the latest HMD position + whichCamera.setHmdPosition(trackerPosition); + whichCamera.setHmdRotation(orientation); + + // Update our camera to what the application camera is doing + _camera->setRotation(whichCamera.getRotation()); + _camera->setPosition(whichCamera.getPosition()); // Store the latest left and right eye render locations for things that need to know glm::vec3 thisEyePosition = position + trackerPosition + @@ -407,10 +555,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p renderDistortionMesh(eyeRenderPose); glBindTexture(GL_TEXTURE_2D, 0); - - // Update camera for use by rest of Interface. - whichCamera.setPosition((_leftEyePosition + _rightEyePosition) / 2.f); - whichCamera.setRotation(_camera->getRotation()); + #endif } diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index 604580a24e..dfe4a212b6 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -18,6 +18,7 @@ #endif #include "renderer/ProgramObject.h" +#include "ui/overlays/BillboardOverlay.h" const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; @@ -30,6 +31,8 @@ public: static void connect(); static void disconnect(); static bool isConnected(); + static void recalibrate(); + static void abandonCalibration(); static void beginFrameTiming(); static void endFrameTiming(); static void configureCamera(Camera& camera, int screenWidth, int screenHeight); @@ -99,6 +102,30 @@ private: static bool _programInitialized; static Camera* _camera; static int _activeEyeIndex; + + static void calibrate(const glm::vec3 position, const glm::quat orientation); + enum CalibrationState { + UNCALIBRATED, + WAITING_FOR_DELTA, + WAITING_FOR_ZERO, + WAITING_FOR_ZERO_HELD, + CALIBRATED + }; + static void positionCalibrationBillboard(BillboardOverlay* billboard); + static float CALIBRATION_DELTA_MINIMUM_LENGTH; + static float CALIBRATION_DELTA_MINIMUM_ANGLE; + static float CALIBRATION_ZERO_MAXIMUM_LENGTH; + static float CALIBRATION_ZERO_MAXIMUM_ANGLE; + static quint64 CALIBRATION_ZERO_HOLD_TIME; + static float CALIBRATION_MESSAGE_DISTANCE; + static CalibrationState _calibrationState; + static glm::vec3 _calibrationPosition; + static glm::quat _calibrationOrientation; + static quint64 _calibrationStartTime; + static int _calibrationMessage; + static QString CALIBRATION_BILLBOARD_URL; + static float CALIBRATION_BILLBOARD_SCALE; + #endif static glm::vec3 _leftEyePosition; diff --git a/interface/src/gpu/Resource.cpp b/interface/src/gpu/Resource.cpp new file mode 100644 index 0000000000..31f53c8201 --- /dev/null +++ b/interface/src/gpu/Resource.cpp @@ -0,0 +1,220 @@ +// +// Resource.cpp +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Resource.h" + +#include + +using namespace gpu; + +Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { + if ( !dataAllocated ) { + qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; + return NOT_ALLOCATED; + } + + // Try to allocate if needed + Size newSize = 0; + if (size > 0) { + // Try allocating as much as the required size + one block of memory + newSize = size; + (*dataAllocated) = new Byte[newSize]; + // Failed? + if (!(*dataAllocated)) { + qWarning() << "Buffer::Sysmem::allocate() : Can't allocate a system memory buffer of " << newSize << "bytes. Fails to create the buffer Sysmem."; + return NOT_ALLOCATED; + } + } + + // Return what's actually allocated + return newSize; +} + +void Resource::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) { + if (dataAllocated) { + delete[] dataAllocated; + } +} + +Resource::Sysmem::Sysmem() : + _stamp(0), + _size(0), + _data(NULL) +{ +} + +Resource::Sysmem::Sysmem(Size size, const Byte* bytes) : + _stamp(0), + _size(0), + _data(NULL) +{ + if (size > 0) { + _size = allocateMemory(&_data, size); + if (_size >= size) { + if (bytes) { + memcpy(_data, bytes, size); + } + } + } +} + +Resource::Sysmem::~Sysmem() { + deallocateMemory( _data, _size ); + _data = NULL; + _size = 0; +} + +Resource::Size Resource::Sysmem::allocate(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return 0; + } + newSize = allocated; + } + // Allocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::resize(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return _size; + } + newSize = allocated; + // Restore back data from old buffer in the new one + if (_data) { + Size copySize = ((newSize < _size)? newSize: _size); + memcpy( newData, _data, copySize); + } + } + // Reallocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) { + if (allocate(size) == size) { + if (bytes) { + memcpy( _data, bytes, _size ); + _stamp++; + } + } + return _size; +} + +Resource::Size Resource::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) { + if (((offset + size) <= getSize()) && bytes) { + memcpy( _data + offset, bytes, size ); + _stamp++; + return size; + } + return 0; +} + +Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) { + if (size > 0) { + Size oldSize = getSize(); + Size totalSize = oldSize + size; + if (resize(totalSize) == totalSize) { + return setSubData(oldSize, size, bytes); + } + } + return 0; +} + +Buffer::Buffer() : + Resource(), + _sysmem(NULL), + _gpuObject(NULL) { + _sysmem = new Sysmem(); +} + +Buffer::~Buffer() { + if (_sysmem) { + delete _sysmem; + _sysmem = 0; + } + if (_gpuObject) { + delete _gpuObject; + _gpuObject = 0; + } +} + +Buffer::Size Buffer::resize(Size size) { + return editSysmem().resize(size); +} + +Buffer::Size Buffer::setData(Size size, const Byte* data) { + return editSysmem().setData(size, data); +} + +Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { + return editSysmem().setSubData( offset, size, data); +} + +Buffer::Size Buffer::append(Size size, const Byte* data) { + return editSysmem().append( size, data); +} + +namespace gpu { +namespace backend { + +BufferObject::~BufferObject() { + if (_buffer!=0) { + glDeleteBuffers(1, &_buffer); + } +} + +void syncGPUObject(const Buffer& buffer) { + BufferObject* object = buffer.getGPUObject(); + + if (object && (object->_stamp == buffer.getSysmem().getStamp())) { + return; + } + + // need to have a gpu object? + if (!object) { + object = new BufferObject(); + glGenBuffers(1, &object->_buffer); + buffer.setGPUObject(object); + } + + // Now let's update the content of the bo with the sysmem version + // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change + //if () { + glBindBuffer(GL_ARRAY_BUFFER, object->_buffer); + glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + object->_stamp = buffer.getSysmem().getStamp(); + object->_size = buffer.getSysmem().getSize(); + //} +} + +}; +}; diff --git a/interface/src/gpu/Resource.h b/interface/src/gpu/Resource.h new file mode 100644 index 0000000000..fbbbe7f7fd --- /dev/null +++ b/interface/src/gpu/Resource.h @@ -0,0 +1,166 @@ +// +// Resource.h +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_Resource_h +#define hifi_gpu_Resource_h + +#include +#include "InterfaceConfig.h" + +namespace gpu { + +class Buffer; +typedef int Stamp; + +// TODO: move the backend namespace into dedicated files, for now we keep it close to the gpu objects definition for convenience +namespace backend { + + class BufferObject { + public: + Stamp _stamp; + GLuint _buffer; + GLuint _size; + + BufferObject() : + _stamp(0), + _buffer(0), + _size(0) + {} + + ~BufferObject(); + }; + void syncGPUObject(const Buffer& buffer); +}; + +class Resource { +public: + typedef unsigned char Byte; + typedef unsigned int Size; + + static const Size NOT_ALLOCATED = -1; + + // The size in bytes of data stored in the resource + virtual Size getSize() const = 0; + +protected: + + Resource() {} + virtual ~Resource() {} + + // Sysmem is the underneath cache for the data in ram of a resource. + class Sysmem { + public: + + Sysmem(); + Sysmem(Size size, const Byte* bytes); + ~Sysmem(); + + Size getSize() const { return _size; } + + // Allocate the byte array + // \param pSize The nb of bytes to allocate, if already exist, content is lost. + // \return The nb of bytes allocated, nothing if allready the appropriate size. + Size allocate(Size pSize); + + // Resize the byte array + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + Size setData(Size size, const Byte* bytes ); + + // Update Sub data, + // doesn't allocate and only copy size * bytes at the offset location + // only if all fits in the existing allocated buffer + Size setSubData(Size offset, Size size, const Byte* bytes); + + // Append new data at the end of the current buffer + // do a resize( size + getSIze) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // Access the byte array. + // The edit version allow to map data. + inline const Byte* readData() const { return _data; } + inline Byte* editData() { _stamp++; return _data; } + + template< typename T > + const T* read() const { return reinterpret_cast< T* > ( _data ); } + template< typename T > + T* edit() { _stamp++; return reinterpret_cast< T* > ( _data ); } + + // Access the current version of the sysmem, used to compare if copies are in sync + inline Stamp getStamp() const { return _stamp; } + + static Size allocateMemory(Byte** memAllocated, Size size); + static void deallocateMemory(Byte* memDeallocated, Size size); + + private: + Sysmem(const Sysmem& sysmem) {} + Sysmem &operator=(const Sysmem& other) {return *this;} + + Stamp _stamp; + Size _size; + Byte* _data; + }; + +}; + +class Buffer : public Resource { +public: + + Buffer(); + Buffer(const Buffer& buf); + ~Buffer(); + + // The size in bytes of data stored in the buffer + inline Size getSize() const { return getSysmem().getSize(); } + inline const Byte* getData() const { return getSysmem().readData(); } + + // Resize the buffer + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + // \return the size of the buffer + Size setData(Size size, const Byte* data); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + // \return the number of bytes copied + Size setSubData(Size offset, Size size, const Byte* data); + + // Append new data at the end of the current buffer + // do a resize( size + getSize) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // this is a temporary hack so the current rendering code can access the underneath gl Buffer Object + // TODO: remove asap, when the backend is doing more of the gl features + inline GLuint getGLBufferObject() const { backend::syncGPUObject(*this); return getGPUObject()->_buffer; } + +protected: + + Sysmem* _sysmem; + + typedef backend::BufferObject GPUObject; + mutable backend::BufferObject* _gpuObject; + + inline const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); } + inline Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); } + + inline GPUObject* getGPUObject() const { return _gpuObject; } + inline void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; } + + friend void backend::syncGPUObject(const Buffer& buffer); +}; + +}; + +#endif diff --git a/interface/src/scripting/JoystickScriptingInterface.cpp b/interface/src/scripting/JoystickScriptingInterface.cpp index 87f841c494..ced063c8fe 100644 --- a/interface/src/scripting/JoystickScriptingInterface.cpp +++ b/interface/src/scripting/JoystickScriptingInterface.cpp @@ -10,8 +10,9 @@ // #include +#include -#ifdef HAVE_SDL +#ifdef HAVE_SDL2 #include #undef main #endif @@ -20,106 +21,106 @@ #include "JoystickScriptingInterface.h" +#ifdef HAVE_SDL2 +SDL_JoystickID getInstanceId(SDL_GameController* controller) { + SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); + return SDL_JoystickInstanceID(joystick); +} +#endif + JoystickScriptingInterface& JoystickScriptingInterface::getInstance() { static JoystickScriptingInterface sharedInstance; return sharedInstance; } JoystickScriptingInterface::JoystickScriptingInterface() : +#ifdef HAVE_SDL2 _openJoysticks(), - _availableDeviceNames(), +#endif _isInitialized(false) { - reset(); +#ifdef HAVE_SDL2 + bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0); + + if (initSuccess) { + int joystickCount = SDL_NumJoysticks(); + + for (int i = 0; i < joystickCount; i++) { + SDL_GameController* controller = SDL_GameControllerOpen(i); + SDL_JoystickID id = getInstanceId(controller); + Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + _openJoysticks[id] = joystick; + } + + _isInitialized = true; + } else { + qDebug() << "Error initializing SDL"; + } +#endif } JoystickScriptingInterface::~JoystickScriptingInterface() { +#ifdef HAVE_SDL2 qDeleteAll(_openJoysticks); -#ifdef HAVE_SDL SDL_Quit(); - _isInitialized = false; #endif } -void JoystickScriptingInterface::reset() { -#ifdef HAVE_SDL - - if (_isInitialized) { - _isInitialized = false; - - // close all the open joysticks before we quit - foreach(Joystick* openJoystick, _openJoysticks) { - openJoystick->closeJoystick(); - } - - SDL_Quit(); - } - - bool initSuccess = (SDL_Init(SDL_INIT_JOYSTICK) == 0); - - if (initSuccess) { - - int joystickCount = SDL_NumJoysticks(); - - for (int i = 0; i < joystickCount; i++) { - _availableDeviceNames << SDL_JoystickName(i); - } - - foreach(const QString& joystickName, _openJoysticks.keys()) { - _openJoysticks[joystickName]->setSDLJoystick(openSDLJoystickWithName(joystickName)); - } - - _isInitialized = true; - } -#endif -} - -void JoystickScriptingInterface::update() { -#ifdef HAVE_SDL - if (_isInitialized) { - PerformanceTimer perfTimer("JoystickScriptingInterface::update"); - SDL_JoystickUpdate(); - - foreach(Joystick* joystick, _openJoysticks) { - joystick->update(); - } +const QObjectList JoystickScriptingInterface::getAllJoysticks() const { + QObjectList objectList; +#ifdef HAVE_SDL2 + const QList joystickList = _openJoysticks.values(); + for (int i = 0; i < joystickList.length(); i++) { + objectList << joystickList[i]; } #endif + return objectList; } Joystick* JoystickScriptingInterface::joystickWithName(const QString& name) { - Joystick* matchingJoystick = _openJoysticks.value(name); -#ifdef HAVE_SDL - if (!matchingJoystick) { - SDL_Joystick* openSDLJoystick = openSDLJoystickWithName(name); - - if (openSDLJoystick) { - matchingJoystick = _openJoysticks.insert(name, new Joystick(name, openSDLJoystick)).value(); - } else { - qDebug() << "No matching joystick found with name" << name << "- returning NULL pointer."; +#ifdef HAVE_SDL2 + QMap::iterator iter = _openJoysticks.begin(); + while (iter != _openJoysticks.end()) { + if (iter.value()->getName() == name) { + return iter.value(); } + iter++; } #endif - - return matchingJoystick; -} - - -#ifdef HAVE_SDL - -SDL_Joystick* JoystickScriptingInterface::openSDLJoystickWithName(const QString &name) { - // we haven't opened a joystick with this name yet - enumerate our SDL devices and see if it exists - int joystickCount = SDL_NumJoysticks(); - - for (int i = 0; i < joystickCount; i++) { - if (SDL_JoystickName(i) == name) { - return SDL_JoystickOpen(i); - break; - } - } - return NULL; } +void JoystickScriptingInterface::update() { +#ifdef HAVE_SDL2 + if (_isInitialized) { + PerformanceTimer perfTimer("JoystickScriptingInterface::update"); + SDL_GameControllerUpdate(); + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_CONTROLLERAXISMOTION) { + Joystick* joystick = _openJoysticks[event.caxis.which]; + if (joystick) { + joystick->handleAxisEvent(event.caxis); + } + } else if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) { + Joystick* joystick = _openJoysticks[event.cbutton.which]; + if (joystick) { + joystick->handleButtonEvent(event.cbutton); + } + } else if (event.type == SDL_CONTROLLERDEVICEADDED) { + SDL_GameController* controller = SDL_GameControllerOpen(event.cdevice.which); + + SDL_JoystickID id = getInstanceId(controller); + Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + _openJoysticks[id] = joystick; + emit joystickAdded(joystick); + } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { + Joystick* joystick = _openJoysticks[event.cdevice.which]; + _openJoysticks.remove(event.cdevice.which); + emit joystickRemoved(joystick); + } + } + } #endif +} diff --git a/interface/src/scripting/JoystickScriptingInterface.h b/interface/src/scripting/JoystickScriptingInterface.h index 02624c70d5..c9a68d24b1 100644 --- a/interface/src/scripting/JoystickScriptingInterface.h +++ b/interface/src/scripting/JoystickScriptingInterface.h @@ -15,34 +15,100 @@ #include #include +#ifdef HAVE_SDL2 +#include +#endif + #include "devices/Joystick.h" /// Handles joystick input through SDL. class JoystickScriptingInterface : public QObject { Q_OBJECT - - Q_PROPERTY(QStringList availableJoystickNames READ getAvailableJoystickNames) + +#ifdef HAVE_SDL2 + Q_PROPERTY(int AXIS_INVALID READ axisInvalid) + Q_PROPERTY(int AXIS_LEFT_X READ axisLeftX) + Q_PROPERTY(int AXIS_LEFT_Y READ axisLeftY) + Q_PROPERTY(int AXIS_RIGHT_X READ axisRightX) + Q_PROPERTY(int AXIS_RIGHT_Y READ axisRightY) + Q_PROPERTY(int AXIS_TRIGGER_LEFT READ axisTriggerLeft) + Q_PROPERTY(int AXIS_TRIGGER_RIGHT READ axisTriggerRight) + Q_PROPERTY(int AXIS_MAX READ axisMax) + + Q_PROPERTY(int BUTTON_INVALID READ buttonInvalid) + Q_PROPERTY(int BUTTON_FACE_BOTTOM READ buttonFaceBottom) + Q_PROPERTY(int BUTTON_FACE_RIGHT READ buttonFaceRight) + Q_PROPERTY(int BUTTON_FACE_LEFT READ buttonFaceLeft) + Q_PROPERTY(int BUTTON_FACE_TOP READ buttonFaceTop) + Q_PROPERTY(int BUTTON_BACK READ buttonBack) + Q_PROPERTY(int BUTTON_GUIDE READ buttonGuide) + Q_PROPERTY(int BUTTON_START READ buttonStart) + Q_PROPERTY(int BUTTON_LEFT_STICK READ buttonLeftStick) + Q_PROPERTY(int BUTTON_RIGHT_STICK READ buttonRightStick) + Q_PROPERTY(int BUTTON_LEFT_SHOULDER READ buttonLeftShoulder) + Q_PROPERTY(int BUTTON_RIGHT_SHOULDER READ buttonRightShoulder) + Q_PROPERTY(int BUTTON_DPAD_UP READ buttonDpadUp) + Q_PROPERTY(int BUTTON_DPAD_DOWN READ buttonDpadDown) + Q_PROPERTY(int BUTTON_DPAD_LEFT READ buttonDpadLeft) + Q_PROPERTY(int BUTTON_DPAD_RIGHT READ buttonDpadRight) + Q_PROPERTY(int BUTTON_MAX READ buttonMax) + + Q_PROPERTY(int BUTTON_PRESSED READ buttonPressed) + Q_PROPERTY(int BUTTON_RELEASED READ buttonRelease) +#endif + public: static JoystickScriptingInterface& getInstance(); - - const QStringList& getAvailableJoystickNames() const { return _availableDeviceNames; } - + void update(); - + public slots: Joystick* joystickWithName(const QString& name); - void reset(); - + const QObjectList getAllJoysticks() const; + +signals: + void joystickAdded(Joystick* joystick); + void joystickRemoved(Joystick* joystick); + private: -#ifdef HAVE_SDL - SDL_Joystick* openSDLJoystickWithName(const QString& name); +#ifdef HAVE_SDL2 + int axisInvalid() const { return SDL_CONTROLLER_AXIS_INVALID; } + int axisLeftX() const { return SDL_CONTROLLER_AXIS_LEFTX; } + int axisLeftY() const { return SDL_CONTROLLER_AXIS_LEFTY; } + int axisRightX() const { return SDL_CONTROLLER_AXIS_RIGHTX; } + int axisRightY() const { return SDL_CONTROLLER_AXIS_RIGHTY; } + int axisTriggerLeft() const { return SDL_CONTROLLER_AXIS_TRIGGERLEFT; } + int axisTriggerRight() const { return SDL_CONTROLLER_AXIS_TRIGGERRIGHT; } + int axisMax() const { return SDL_CONTROLLER_AXIS_MAX; } + + int buttonInvalid() const { return SDL_CONTROLLER_BUTTON_INVALID; } + int buttonFaceBottom() const { return SDL_CONTROLLER_BUTTON_A; } + int buttonFaceRight() const { return SDL_CONTROLLER_BUTTON_B; } + int buttonFaceLeft() const { return SDL_CONTROLLER_BUTTON_X; } + int buttonFaceTop() const { return SDL_CONTROLLER_BUTTON_Y; } + int buttonBack() const { return SDL_CONTROLLER_BUTTON_BACK; } + int buttonGuide() const { return SDL_CONTROLLER_BUTTON_GUIDE; } + int buttonStart() const { return SDL_CONTROLLER_BUTTON_START; } + int buttonLeftStick() const { return SDL_CONTROLLER_BUTTON_LEFTSTICK; } + int buttonRightStick() const { return SDL_CONTROLLER_BUTTON_RIGHTSTICK; } + int buttonLeftShoulder() const { return SDL_CONTROLLER_BUTTON_LEFTSHOULDER; } + int buttonRightShoulder() const { return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; } + int buttonDpadUp() const { return SDL_CONTROLLER_BUTTON_DPAD_UP; } + int buttonDpadDown() const { return SDL_CONTROLLER_BUTTON_DPAD_DOWN; } + int buttonDpadLeft() const { return SDL_CONTROLLER_BUTTON_DPAD_LEFT; } + int buttonDpadRight() const { return SDL_CONTROLLER_BUTTON_DPAD_RIGHT; } + int buttonMax() const { return SDL_CONTROLLER_BUTTON_MAX; } + + int buttonPressed() const { return SDL_PRESSED; } + int buttonRelease() const { return SDL_RELEASED; } #endif - + JoystickScriptingInterface(); ~JoystickScriptingInterface(); - - QMap _openJoysticks; - QStringList _availableDeviceNames; + +#ifdef HAVE_SDL2 + QMap _openJoysticks; +#endif bool _isInitialized; }; diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index ea9d645a94..21a5244138 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -22,6 +22,9 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting) { QSettings* settings = Application::getInstance()->lockSettings(); QVariant value = settings->value(setting); Application::getInstance()->unlockSettings(); + if (!value.isValid()) { + value = ""; + } return value; } @@ -29,6 +32,9 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar QSettings* settings = Application::getInstance()->lockSettings(); QVariant value = settings->value(setting, defaultValue); Application::getInstance()->unlockSettings(); + if (!value.isValid()) { + value = ""; + } return value; } diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 45911d9626..ee7cf63789 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -129,6 +129,7 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new EraseHeightfieldTool(this)); addTool(new VoxelMaterialBoxTool(this)); addTool(new VoxelMaterialSpannerTool(this)); + addTool(new VoxelMaterialBrushTool(this)); updateAttributes(); @@ -1525,3 +1526,102 @@ void VoxelMaterialSpannerTool::updateTexture() { void VoxelMaterialSpannerTool::textureLoaded() { _color->setColor(_texture->getAverageColor()); } + +VoxelBrushTool::VoxelBrushTool(MetavoxelEditor* editor, const QString& name) : + MetavoxelTool(editor, name, false, true) { + + QWidget* widget = new QWidget(); + widget->setLayout(_form = new QFormLayout()); + layout()->addWidget(widget); + + _form->addRow("Radius:", _radius = new QDoubleSpinBox()); + _radius->setSingleStep(0.01); + _radius->setMaximum(FLT_MAX); + _radius->setValue(0.25); +} + +bool VoxelBrushTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("VoxelColorAttribute"); +} + +void VoxelBrushTool::render() { + if (Application::getInstance()->isMouseHidden()) { + return; + } + + // find the intersection with the voxels + glm::vec3 origin = Application::getInstance()->getMouseRayOrigin(); + glm::vec3 direction = Application::getInstance()->getMouseRayDirection(); + + float distance; + if (!Application::getInstance()->getMetavoxels()->findFirstRayVoxelIntersection(origin, direction, distance)) { + return; + } + Application::getInstance()->getMetavoxels()->renderVoxelCursor( + _position = origin + distance * direction, _radius->value()); +} + +bool VoxelBrushTool::eventFilter(QObject* watched, QEvent* event) { + if (event->type() == QEvent::Wheel) { + float angle = static_cast(event)->angleDelta().y(); + const float ANGLE_SCALE = 1.0f / 1000.0f; + _radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE)); + return true; + + } else if (event->type() == QEvent::MouseButtonPress) { + MetavoxelEditMessage message = { createEdit(static_cast(event)->button() == Qt::RightButton) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); + return true; + } + return false; +} + +VoxelMaterialBrushTool::VoxelMaterialBrushTool(MetavoxelEditor* editor) : + VoxelBrushTool(editor, "Material Brush") { + + _form->addRow("Color:", _color = new QColorEditor(this)); + connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBrushTool::clearTexture); + _form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false)); + connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBrushTool::updateTexture); +} + +QVariant VoxelMaterialBrushTool::createEdit(bool alternate) { + if (alternate) { + return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); + } else { + SharedObjectPointer material = _materialEditor->getObject(); + if (static_cast(material.data())->getDiffuse().isValid()) { + _materialEditor->detachObject(); + } else { + material = SharedObjectPointer(); + } + return QVariant::fromValue(PaintVoxelMaterialEdit(_position, _radius->value(), material, _color->getColor())); + } +} + +void VoxelMaterialBrushTool::clearTexture() { + _materialEditor->setObject(new MaterialObject()); +} + +void VoxelMaterialBrushTool::updateTexture() { + if (_texture) { + _texture->disconnect(this); + } + MaterialObject* material = static_cast(_materialEditor->getObject().data()); + if (!material->getDiffuse().isValid()) { + _texture.clear(); + return; + } + _texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE); + if (_texture) { + if (_texture->isLoaded()) { + textureLoaded(); + } else { + connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialBrushTool::textureLoaded); + } + } +} + +void VoxelMaterialBrushTool::textureLoaded() { + _color->setColor(_texture->getAverageColor()); +} diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index ec92d9de28..debe45ea2f 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -469,4 +469,53 @@ private: QSharedPointer _texture; }; +/// Base class for voxel brush tools. +class VoxelBrushTool : public MetavoxelTool { + Q_OBJECT + +public: + + VoxelBrushTool(MetavoxelEditor* editor, const QString& name); + + virtual bool appliesTo(const AttributePointer& attribute) const; + + virtual void render(); + + virtual bool eventFilter(QObject* watched, QEvent* event); + +protected: + + virtual QVariant createEdit(bool alternate) = 0; + + QFormLayout* _form; + QDoubleSpinBox* _radius; + + glm::vec3 _position; +}; + +/// Allows texturing parts of the voxel field. +class VoxelMaterialBrushTool : public VoxelBrushTool { + Q_OBJECT + +public: + + VoxelMaterialBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +private slots: + + void clearTexture(); + void updateTexture(); + void textureLoaded(); + +private: + + QColorEditor* _color; + SharedObjectEditor* _materialEditor; + QSharedPointer _texture; +}; + #endif // hifi_MetavoxelEditor_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6b7d499bf9..dd9ac67837 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -690,7 +690,6 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); } - } void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress, diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp index c4f6b328ef..8c78a06d55 100644 --- a/interface/src/ui/TextRenderer.cpp +++ b/interface/src/ui/TextRenderer.cpp @@ -21,6 +21,10 @@ #include "InterfaceConfig.h" #include "TextRenderer.h" +#include "glm/glm.hpp" +#include + + // the width/height of the cached glyph textures const int IMAGE_SIZE = 256; @@ -63,8 +67,17 @@ int TextRenderer::calculateHeight(const char* str) { } int TextRenderer::draw(int x, int y, const char* str) { - glEnable(GL_TEXTURE_2D); - + // Grab the current color + float currentColor[4]; + glGetFloatv(GL_CURRENT_COLOR, currentColor); + int compactColor = ((int( currentColor[0] * 255.f) & 0xFF)) | + ((int( currentColor[1] * 255.f) & 0xFF) << 8) | + ((int( currentColor[2] * 255.f) & 0xFF) << 16) | + ((int( currentColor[3] * 255.f) & 0xFF) << 24); + +// TODO: Remove that code once we test for performance improvments + //glEnable(GL_TEXTURE_2D); + int maxHeight = 0; for (const char* ch = str; *ch != 0; ch++) { const Glyph& glyph = getGlyph(*ch); @@ -76,20 +89,24 @@ int TextRenderer::draw(int x, int y, const char* str) { if (glyph.bounds().height() > maxHeight) { maxHeight = glyph.bounds().height(); } - - glBindTexture(GL_TEXTURE_2D, glyph.textureID()); + //glBindTexture(GL_TEXTURE_2D, glyph.textureID()); int left = x + glyph.bounds().x(); int right = x + glyph.bounds().x() + glyph.bounds().width(); int bottom = y + glyph.bounds().y(); int top = y + glyph.bounds().y() + glyph.bounds().height(); - + + glm::vec2 leftBottom = glm::vec2(float(left), float(bottom)); + glm::vec2 rightTop = glm::vec2(float(right), float(top)); + float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE; float ls = glyph.location().x() * scale; float rs = (glyph.location().x() + glyph.bounds().width()) * scale; float bt = glyph.location().y() * scale; float tt = (glyph.location().y() + glyph.bounds().height()) * scale; - + +// TODO: Remove that code once we test for performance improvments +/* glBegin(GL_QUADS); glTexCoord2f(ls, bt); glVertex2f(left, bottom); @@ -100,12 +117,39 @@ int TextRenderer::draw(int x, int y, const char* str) { glTexCoord2f(ls, tt); glVertex2f(left, top); glEnd(); - +*/ + + const int NUM_COORDS_SCALARS_PER_GLYPH = 16; + float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt, + rightTop.x, leftBottom.y, rs, bt, + rightTop.x, rightTop.y, rs, tt, + leftBottom.x, rightTop.y, ls, tt, }; + + const int NUM_COLOR_SCALARS_PER_GLYPH = 4; + unsigned int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor }; + + gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched; + gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched; + if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer.getSize()) { + _glyphsBuffer.append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } else { + _glyphsBuffer.setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } + _numGlyphsBatched++; + x += glyph.width(); } - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - + + // TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call + drawBatch(); + clearBatch(); + +// TODO: Remove that code once we test for performance improvments + // glBindTexture(GL_TEXTURE_2D, 0); + // glDisable(GL_TEXTURE_2D); + return maxHeight; } @@ -131,8 +175,10 @@ TextRenderer::TextRenderer(const Properties& properties) : _x(IMAGE_SIZE), _y(IMAGE_SIZE), _rowHeight(0), - _color(properties.color) { - + _color(properties.color), + _glyphsBuffer(), + _numGlyphsBatched(0) +{ _font.setKerning(false); } @@ -228,9 +274,74 @@ const Glyph& TextRenderer::getGlyph(char c) { return glyph; } +void TextRenderer::drawBatch() { + if (_numGlyphsBatched <= 0) { + return; + } + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + GLint matrixMode; + glGetIntegerv(GL_MATRIX_MODE, &matrixMode); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + */ + + glEnable(GL_TEXTURE_2D); + // TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + + GLuint vbo = _glyphsBuffer.getGLBufferObject(); + GLuint colorvbo = _glyphsColorBuffer.getGLBufferObject(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + const int NUM_POS_COORDS = 2; + const int NUM_TEX_COORDS = 2; + const int VERTEX_STRIDE = (NUM_POS_COORDS + NUM_TEX_COORDS) * sizeof(float); + const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexPointer(2, GL_FLOAT, VERTEX_STRIDE, 0); + glTexCoordPointer(2, GL_FLOAT, VERTEX_STRIDE, (GLvoid*) VERTEX_TEXCOORD_OFFSET ); + + glBindBuffer(GL_ARRAY_BUFFER, colorvbo); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*) 0 ); + + glDrawArrays(GL_QUADS, 0, _numGlyphsBatched * 4); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(matrixMode); + */ +} + +void TextRenderer::clearBatch() { + _numGlyphsBatched = 0; +} + QHash TextRenderer::_instances; Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) : _textureID(textureID), _location(location), _bounds(bounds), _width(width) { } - diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h index d3340462d5..1953dda422 100644 --- a/interface/src/ui/TextRenderer.h +++ b/interface/src/ui/TextRenderer.h @@ -19,6 +19,8 @@ #include #include +#include "gpu/Resource.h" + #include "InterfaceConfig.h" // a special "character" that renders as a solid block @@ -64,6 +66,8 @@ public: int computeWidth(char ch); int computeWidth(const char* str); + void drawBatch(); + void clearBatch(); private: TextRenderer(const Properties& properties); @@ -100,6 +104,11 @@ private: // text color QColor _color; + // Graphics Buffer containing the current accumulated glyphs to render + gpu::Buffer _glyphsBuffer; + gpu::Buffer _glyphsColorBuffer; + int _numGlyphsBatched; + static QHash _instances; }; diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 4f9e7c84f6..c52b07bcfa 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -15,10 +15,12 @@ #include "BillboardOverlay.h" -BillboardOverlay::BillboardOverlay() -: _fromImage(-1,-1,-1,-1), - _scale(1.0f), - _isFacingAvatar(true) { +BillboardOverlay::BillboardOverlay() : + _newTextureNeeded(true), + _fromImage(-1,-1,-1,-1), + _scale(1.0f), + _isFacingAvatar(true) +{ _isLoaded = false; } @@ -28,6 +30,9 @@ void BillboardOverlay::render() { } if (!_billboard.isEmpty()) { + if (_newTextureNeeded && _billboardTexture) { + _billboardTexture.reset(); + } if (!_billboardTexture) { QImage image = QImage::fromData(_billboard); if (image.format() != QImage::Format_ARGB32) { @@ -38,6 +43,7 @@ void BillboardOverlay::render() { _fromImage.setRect(0, 0, _size.width(), _size.height()); } _billboardTexture.reset(new Texture()); + _newTextureNeeded = false; glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); @@ -107,9 +113,10 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { QScriptValue urlValue = properties.property("url"); if (urlValue.isValid()) { - _url = urlValue.toVariant().toString(); - - setBillboardURL(_url); + QString newURL = urlValue.toVariant().toString(); + if (newURL != _url) { + setBillboardURL(newURL); + } } QScriptValue subImageBounds = properties.property("subImage"); @@ -150,9 +157,21 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { } } -void BillboardOverlay::setBillboardURL(const QUrl url) { +void BillboardOverlay::setURL(const QString& url) { + setBillboardURL(url); +} + +void BillboardOverlay::setBillboardURL(const QString& url) { + _url = url; + QUrl actualURL = url; + _isLoaded = false; - QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(url)); + + // clear the billboard if previously set + _billboard.clear(); + _newTextureNeeded = true; + + QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(actualURL)); connect(reply, &QNetworkReply::finished, this, &BillboardOverlay::replyFinished); } diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index a0b76869b3..c0c2b05364 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -24,6 +24,12 @@ public: BillboardOverlay(); virtual void render(); + + // setters + void setURL(const QString& url); + void setScale(float scale) { _scale = scale; } + void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; } + virtual void setProperties(const QScriptValue& properties); void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } @@ -33,12 +39,13 @@ private slots: void replyFinished(); private: - void setBillboardURL(const QUrl url); + void setBillboardURL(const QString& url); - QUrl _url; + QString _url; QByteArray _billboard; QSize _size; QScopedPointer _billboardTexture; + bool _newTextureNeeded; QRect _fromImage; // where from in the image to sample diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 6ff256d48e..d7120a5ff8 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Circle3DOverlay.h" #include "renderer/GlowEffect.h" @@ -39,13 +40,18 @@ void Circle3DOverlay::render() { if (!_visible) { return; // do nothing if we're not visible } + + float alpha = getAlpha(); + + if (alpha == 0.0) { + return; // do nothing if our alpha is 0, we're not visible + } const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; //const int slices = 15; - float alpha = getAlpha(); xColor color = getColor(); const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); @@ -162,7 +168,8 @@ void Circle3DOverlay::render() { xColor color = getMajorTickMarksColor(); glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); - float angle = startAt; + float tickMarkAngle = getMajorTickMarksAngle(); + float angle = startAt - fmod(startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMajorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; @@ -177,7 +184,7 @@ void Circle3DOverlay::render() { glVertex2f(thisPointA.x, thisPointA.y); glVertex2f(thisPointB.x, thisPointB.y); - angle += getMajorTickMarksAngle(); + angle += tickMarkAngle; } } @@ -187,7 +194,8 @@ void Circle3DOverlay::render() { xColor color = getMinorTickMarksColor(); glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); - float angle = startAt; + float tickMarkAngle = getMinorTickMarksAngle(); + float angle = startAt - fmod(startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMinorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; @@ -202,7 +210,7 @@ void Circle3DOverlay::render() { glVertex2f(thisPointA.x, thisPointA.y); glVertex2f(thisPointB.x, thisPointB.y); - angle += getMinorTickMarksAngle(); + angle += tickMarkAngle; } } @@ -291,6 +299,25 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) { } } +bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, + const glm::vec3& direction, float& distance, BoxFace& face) const { + + bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face); + if (intersects) { + glm::vec3 hitAt = origin + (direction * distance); + float distanceToHit = glm::distance(hitAt, _position); + + float maxDimension = glm::max(_dimensions.x, _dimensions.y); + float innerRadius = maxDimension * getInnerRadius(); + float outerRadius = maxDimension * getOuterRadius(); + + // TODO: this really only works for circles, we should be handling the ellipse case as well... + if (distanceToHit < innerRadius || distanceToHit > outerRadius) { + intersects = false; + } + } + return intersects; +} diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 791d951105..289781cdd7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -45,6 +45,8 @@ public: void setMinorTickMarksLength(float value) { _minorTickMarksLength = value; } void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; protected: float _startAt; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index fe9a9edca0..922c6a0408 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -124,90 +124,59 @@ void Overlays::render3D() { unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) { unsigned int thisID = 0; - bool created = false; - bool is3D = false; Overlay* thisOverlay = NULL; + bool created = true; if (type == "image") { thisOverlay = new ImageOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; } else if (type == "text") { thisOverlay = new TextOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; } else if (type == "cube") { thisOverlay = new Cube3DOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "sphere") { thisOverlay = new Sphere3DOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "circle3d") { thisOverlay = new Circle3DOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "rectangle3d") { thisOverlay = new Rectangle3DOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "line3d") { thisOverlay = new Line3DOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "localvoxels") { thisOverlay = new LocalVoxelsOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "localmodels") { thisOverlay = new LocalModelsOverlay(Application::getInstance()->getEntityClipboardRenderer()); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "model") { thisOverlay = new ModelOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; } else if (type == "billboard") { thisOverlay = new BillboardOverlay(); - thisOverlay->init(_parent); - thisOverlay->setProperties(properties); - created = true; - is3D = true; + } else { + created = false; } if (created) { - QWriteLocker lock(&_lock); - thisID = _nextOverlayID; - _nextOverlayID++; - if (is3D) { - _overlays3D[thisID] = thisOverlay; - } else { - _overlays2D[thisID] = thisOverlay; - } + thisOverlay->setProperties(properties); + thisID = addOverlay(thisOverlay); } return thisID; } +unsigned int Overlays::addOverlay(Overlay* overlay) { + overlay->init(_parent); + + QWriteLocker lock(&_lock); + unsigned int thisID = _nextOverlayID; + _nextOverlayID++; + bool is3D = typeid(*overlay) != typeid(ImageOverlay) && typeid(*overlay) != typeid(TextOverlay); + if (is3D) { + _overlays3D[thisID] = overlay; + } else { + _overlays2D[thisID] = overlay; + } + + return thisID; +} + bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) { Overlay* thisOverlay = NULL; QWriteLocker lock(&_lock); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 6676994eed..4851b1f9dc 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -44,6 +44,9 @@ public slots: /// adds an overlay with the specific properties unsigned int addOverlay(const QString& type, const QScriptValue& properties); + /// adds an overlay that's already been created + unsigned int addOverlay(Overlay* overlay); + /// edits an overlay updating only the included properties, will return the identified OverlayID in case of /// successful edit, if the input id is for an unknown overlay this function will have no effect bool editOverlay(unsigned int id, const QScriptValue& properties); diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index 42e059c3ca..91a3a023f7 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -12,6 +12,8 @@ #include "InterfaceConfig.h" #include +#include +#include #include #include @@ -74,3 +76,29 @@ void Planar3DOverlay::setProperties(const QScriptValue& properties) { } } } + +bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + RayIntersectionInfo rayInfo; + rayInfo._rayStart = origin; + rayInfo._rayDirection = direction; + rayInfo._rayLength = std::numeric_limits::max(); + + PlaneShape plane; + + const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + plane.setNormal(normal); + plane.setPoint(_position); // the position is definitely a point on our plane + + bool intersects = plane.findRayIntersection(rayInfo); + + if (intersects) { + distance = rayInfo._hitDistance; + // TODO: if it intersects, we want to check to see if the intersection point is within our dimensions + // glm::vec3 hitAt = origin + direction * distance; + // _dimensions + } + return intersects; +} diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index c1733bc0fb..ee4bb3e05a 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -37,6 +37,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: glm::vec2 _dimensions; }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 1080a866f5..90f56e36b9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -174,6 +174,18 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons sittingPoints.setProperty("length", _sittingPoints.size()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable + AABox aaBox = getAABoxInMeters(); + QScriptValue boundingBox = engine->newObject(); + QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner()); + QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft()); + QScriptValue center = vec3toScriptValue(engine, aaBox.calcCenter()); + QScriptValue boundingBoxDimensions = vec3toScriptValue(engine, aaBox.getDimensions()); + boundingBox.setProperty("brn", bottomRightNear); + boundingBox.setProperty("tfl", topFarLeft); + boundingBox.setProperty("center", center); + boundingBox.setProperty("dimensions", boundingBoxDimensions); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(boundingBox, boundingBox); // gettable, but not settable + return properties; } @@ -643,3 +655,20 @@ AACube EntityItemProperties::getMaximumAACubeInMeters() const { return AACube(minimumCorner, diameter); } + +// The minimum bounding box for the entity. +AABox EntityItemProperties::getAABoxInMeters() const { + + // _position represents the position of the registration point. + glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; + + glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; + Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + + // shift the extents to be relative to the position/registration point + rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position); + + return AABox(rotatedExtentsRelativeToRegistrationPoint); +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3c77b63ab6..6e1594fb9b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -101,6 +101,7 @@ public: AACube getMaximumAACubeInTreeUnits() const; AACube getMaximumAACubeInMeters() const; + AABox getAABoxInMeters() const; void debugDump() const; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 44c333a03f..a625c71d6a 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -70,7 +70,7 @@ AttributeRegistry::AttributeRegistry() : _heightfieldColorAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); _heightfieldMaterialAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); - const float VOXEL_LOD_THRESHOLD_MULTIPLIER = 32.0f; + const float VOXEL_LOD_THRESHOLD_MULTIPLIER = 16.0f; _voxelColorAttribute->setLODThresholdMultiplier(VOXEL_LOD_THRESHOLD_MULTIPLIER); _voxelColorAttribute->setUserFacing(true); _voxelMaterialAttribute->setLODThresholdMultiplier(VOXEL_LOD_THRESHOLD_MULTIPLIER); @@ -2112,53 +2112,41 @@ bool VoxelHermiteAttribute::merge(void*& parent, void* children[], bool postRead QRgb* dest = contents.data() + ((zIndex * halfSize * area) + (yIndex * halfSize * size) + (xIndex * halfSize)) * VoxelHermiteData::EDGE_COUNT; const QRgb* src = childContents.data(); + int offsets[VoxelHermiteData::EDGE_COUNT]; if (childSize == size) { // simple case: one destination value for four child values for (int z = 0; z < halfSizeComplement; z++) { - int offset4 = (z == halfSize) ? 0 : (childArea * VoxelHermiteData::EDGE_COUNT); + offsets[2] = (z == halfSize) ? 0 : (childArea * VoxelHermiteData::EDGE_COUNT); for (int y = 0; y < halfSizeComplement; y++) { - int offset2 = (y == halfSize) ? 0 : (childSize * VoxelHermiteData::EDGE_COUNT); - int offset6 = offset4 + offset2; + offsets[1] = (y == halfSize) ? 0 : (childSize * VoxelHermiteData::EDGE_COUNT); for (QRgb* end = dest + halfSizeComplement * VoxelHermiteData::EDGE_COUNT; dest != end; dest += VoxelHermiteData::EDGE_COUNT) { - int offset1 = (dest == end - VoxelHermiteData::EDGE_COUNT) ? 0 : VoxelHermiteData::EDGE_COUNT; + offsets[0] = (dest == end - VoxelHermiteData::EDGE_COUNT) ? 0 : VoxelHermiteData::EDGE_COUNT; for (int i = 0; i < VoxelHermiteData::EDGE_COUNT; i++) { - QRgb v[] = { src[i], src[offset1 + i], src[offset2 + i], src[offset2 + offset1 + i], - src[offset4 + i], src[offset4 + offset1 + i], src[offset6 + i], src[offset6 + offset1 + i] }; - glm::vec3 n[] = { unpackNormal(v[0]), unpackNormal(v[1]), unpackNormal(v[2]), unpackNormal(v[3]), - unpackNormal(v[4]), unpackNormal(v[5]), unpackNormal(v[6]), unpackNormal(v[7]) }; - float l[] = { glm::length(n[0]), glm::length(n[1]), glm::length(n[2]), glm::length(n[3]), - glm::length(n[4]), glm::length(n[5]), glm::length(n[6]), glm::length(n[7]) }; - float lengthTotal = l[0] + l[1] + l[2] + l[3] + l[4] + l[5] + l[6] + l[7]; + QRgb v0 = src[i], v1 = src[i + offsets[i]]; + glm::vec3 n0 = unpackNormal(v0), n1 = unpackNormal(v1); + float l0 = glm::length(n0), l1 = glm::length(n1); + float lengthTotal = l0 + l1; if (lengthTotal == 0.0f) { dest[i] = qRgba(0, 0, 0, 0); continue; } - glm::vec3 combinedNormal = n[0] * l[0] + n[1] * l[1] + n[2] * l[2] + n[3] * l[3] + n[4] * l[4] + - n[5] * l[5] + n[6] * l[6] + n[7] * l[7]; + glm::vec3 combinedNormal = n0 + n1; float combinedLength = glm::length(combinedNormal); if (combinedLength > 0.0f) { combinedNormal /= combinedLength; } - float combinedOffset = 0.0f; - int mask = 1 << i; - for (int j = 0; j < MERGE_COUNT; j++) { - float offset = qAlpha(v[j]) * (0.5f / EIGHT_BIT_MAXIMUM); - if (j & mask) { - offset += 0.5f; - } - combinedOffset += offset * l[j]; - } - dest[i] = packNormal(combinedNormal, EIGHT_BIT_MAXIMUM * combinedOffset / lengthTotal); + float combinedOffset = qAlpha(v0) * 0.5f * l0 + (qAlpha(v1) + EIGHT_BIT_MAXIMUM) * 0.5f * l1; + dest[i] = packNormal(combinedNormal, combinedOffset / lengthTotal); } - src += (VoxelHermiteData::EDGE_COUNT + offset1); + src += (VoxelHermiteData::EDGE_COUNT + offsets[0]); } dest += (halfSize * VoxelHermiteData::EDGE_COUNT); - src += offset2; + src += offsets[1]; } dest += (halfSize * size * VoxelHermiteData::EDGE_COUNT); - src += offset4; + src += offsets[2]; } } else { // more complex: N destination values for four child values diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 1a18079257..29fbe182d3 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -848,3 +848,108 @@ void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObject VoxelMaterialSpannerEditVisitor visitor(spanner, material, averageColor); data.guide(visitor); } + +PaintVoxelMaterialEdit::PaintVoxelMaterialEdit(const glm::vec3& position, float radius, + const SharedObjectPointer& material, const QColor& averageColor) : + position(position), + radius(radius), + material(material), + averageColor(averageColor) { +} + +class PaintVoxelMaterialEditVisitor : public MetavoxelVisitor { +public: + + PaintVoxelMaterialEditVisitor(const glm::vec3& position, float radius, + const SharedObjectPointer& material, const QColor& color); + + virtual int visit(MetavoxelInfo& info); + +private: + + glm::vec3 _position; + float _radius; + SharedObjectPointer _material; + QColor _color; + Box _bounds; +}; + +PaintVoxelMaterialEditVisitor::PaintVoxelMaterialEditVisitor(const glm::vec3& position, float radius, + const SharedObjectPointer& material, const QColor& color) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getVoxelColorAttribute() << + AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector() << + AttributeRegistry::getInstance()->getVoxelColorAttribute() << + AttributeRegistry::getInstance()->getVoxelMaterialAttribute()), + _position(position), + _radius(radius), + _material(material), + _color(color) { + + glm::vec3 extents(_radius, _radius, _radius); + _bounds = Box(_position - extents, _position + extents); +} + +int PaintVoxelMaterialEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue(); + VoxelMaterialDataPointer materialPointer = info.inputValues.at(1).getInlineValue(); + if (!(colorPointer && materialPointer && colorPointer->getSize() == materialPointer->getSize())) { + return STOP_RECURSION; + } + QVector colorContents = colorPointer->getContents(); + QByteArray materialContents = materialPointer->getContents(); + QVector materials = materialPointer->getMaterials(); + + Box overlap = info.getBounds().getIntersection(_bounds); + int size = colorPointer->getSize(); + int area = size * size; + float scale = (size - 1.0f) / info.size; + overlap.minimum = (overlap.minimum - info.minimum) * scale; + overlap.maximum = (overlap.maximum - info.minimum) * scale; + int minX = glm::ceil(overlap.minimum.x); + int minY = glm::ceil(overlap.minimum.y); + int minZ = glm::ceil(overlap.minimum.z); + int sizeX = (int)overlap.maximum.x - minX + 1; + int sizeY = (int)overlap.maximum.y - minY + 1; + int sizeZ = (int)overlap.maximum.z - minZ + 1; + + QRgb rgb = _color.rgba(); + float step = 1.0f / scale; + glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step); + uchar materialIndex = getMaterialIndex(_material, materials, materialContents); + QRgb* colorData = colorContents.data(); + uchar* materialData = (uchar*)materialContents.data(); + for (int destZ = minZ * area + minY * size + minX, endZ = destZ + sizeZ * area; destZ != endZ; + destZ += area, position.z += step) { + position.y = info.minimum.y + minY * step; + for (int destY = destZ, endY = destY + sizeY * size; destY != endY; destY += size, position.y += step) { + position.x = info.minimum.x + minX * step; + for (int destX = destY, endX = destX + sizeX; destX != endX; destX++, position.x += step) { + QRgb& color = colorData[destX]; + if (qAlpha(color) != 0 && glm::distance(position, _position) <= _radius) { + color = rgb; + materialData[destX] = materialIndex; + } + } + } + } + VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, size)); + info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(), + encodeInline(newColorPointer)); + + clearUnusedMaterials(materials, materialContents); + VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, size, materials)); + info.outputValues[1] = AttributeValue(_inputs.at(1), encodeInline(newMaterialPointer)); + + return STOP_RECURSION; +} + +void PaintVoxelMaterialEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + PaintVoxelMaterialEditVisitor visitor(position, radius, material, averageColor); + data.guide(visitor); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index bf59a1e691..0ac0bf59ec 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -261,4 +261,23 @@ public: DECLARE_STREAMABLE_METATYPE(VoxelMaterialSpannerEdit) +/// An edit that sets a region of a voxel material. +class PaintVoxelMaterialEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 position; + STREAM float radius; + STREAM SharedObjectPointer material; + STREAM QColor averageColor; + + PaintVoxelMaterialEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, + const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor()); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(PaintVoxelMaterialEdit) + #endif // hifi_MetavoxelMessages_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 14bcd7c3ce..eddd63ca7a 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -85,7 +85,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 4; + return 6; case PacketTypeVoxelData: return VERSION_VOXELS_HAS_FILE_BREAKS; default: diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index 689ce2c747..f88df3b7c0 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "Vec3.h" @@ -37,7 +39,7 @@ glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) { return v1 + v2; } glm::vec3 Vec3::subtract(const glm::vec3& v1, const glm::vec3& v2) { - return v1 - v2; + return v1 - v2; } float Vec3::length(const glm::vec3& v) { @@ -48,6 +50,10 @@ float Vec3::distance(const glm::vec3& v1, const glm::vec3& v2) { return glm::distance(v1, v2); } +float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { + return glm::degrees(glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3))); +} + glm::vec3 Vec3::normalize(const glm::vec3& v) { return glm::normalize(v); } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 2af1350e4a..693fd604f7 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -34,6 +34,7 @@ public slots: glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2); float length(const glm::vec3& v); float distance(const glm::vec3& v1, const glm::vec3& v2); + float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3); glm::vec3 normalize(const glm::vec3& v); void print(const QString& lable, const glm::vec3& v); bool equal(const glm::vec3& v1, const glm::vec3& v2);