mirror of
https://github.com/overte-org/overte.git
synced 2025-04-19 12:23:24 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into entity-tools
This commit is contained in:
commit
f178de3b2e
55 changed files with 3434 additions and 1206 deletions
249
cmake/modules/FindSDL2.cmake
Normal file
249
cmake/modules/FindSDL2.cmake
Normal file
|
@ -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 <SDL2/SDL.h>. 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)
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = "<div class='form-group" + (isAdvanced ? " advanced-setting" : "") + "'>"
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>"
|
||||
|
||||
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 += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<label for='" + setting_name + "'>"
|
||||
form_group += "<input type='checkbox' name='" + setting_name + "' " +
|
||||
(setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += "<input type='checkbox'" + common_attrs + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += " " + setting.help + "</label>";
|
||||
form_group += "</div>"
|
||||
} else if (setting.type === 'table') {
|
||||
|
@ -46,15 +58,15 @@ var viewHelpers = {
|
|||
|
||||
form_group += "</select>"
|
||||
|
||||
form_group += "<input type='hidden' name='" + setting_name + "' value='" + setting_value + "'>"
|
||||
form_group += "<input type='hidden'" + common_attrs + "value='" + setting_value + "'>"
|
||||
} else {
|
||||
|
||||
if (input_type == 'integer') {
|
||||
input_type = "text"
|
||||
}
|
||||
|
||||
form_group += "<input type='" + input_type + "' class='form-control' name='" + setting_name +
|
||||
"' placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
}
|
||||
|
||||
|
@ -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("<input type='hidden' class='form-control' name='" + row.attr("name") + "' data-changed='true' value=''>");
|
||||
$('#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 = "<label class='control-label'>" + setting.label + "</label>"
|
||||
html += "<span class='help-block'>" + setting.help + "</span>"
|
||||
html += "<table class='table table-bordered' name='" + setting_name + "'>"
|
||||
html += "<table class='table table-bordered' data-short-name='" + setting.name + "' name='" + setting_name
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
|
||||
|
||||
// Column names
|
||||
html += "<tr class='headers'>"
|
||||
if (setting.number === true) {
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='number'><strong>#</strong></td>" // Row number
|
||||
}
|
||||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
if (setting.can_delete === true || setting.can_add === true) {
|
||||
html += "<td class='buttons'><strong>+/-</strong></td>" // Buttons
|
||||
}
|
||||
html += "</tr>"
|
||||
|
||||
html += "<td class='buttons'><strong>+/-</strong></td></tr>"
|
||||
|
||||
// Rows
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1
|
||||
_.each(setting_value, function(row, name) {
|
||||
html += "<tr class='row-data' name='" + setting_name + "." + name + "'>"
|
||||
|
||||
_.each(setting_value, function(row, indexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + setting_name + "." + indexOrName + "'") + ">"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
}
|
||||
html += "<td class='key'>" + name + "</td>"
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'>" + indexOrName + "</td>"
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='row-data'>"
|
||||
if (row.hasOwnProperty(col.name)) {
|
||||
html += row[col.name]
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
||||
|
||||
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 += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
|
||||
+ (colIsArray ? "" : "." + col.name) + "' value='" + colValue + "'/>"
|
||||
} else if (row.hasOwnProperty(col.name)) {
|
||||
html += row[col.name]
|
||||
}
|
||||
|
||||
html += "</td>"
|
||||
})
|
||||
if (setting.can_delete === true) {
|
||||
html += "<td class='buttons'><span class='glyphicon glyphicon-remove del-row'></span></td>"
|
||||
} else if (setting.can_add === true) {
|
||||
html += "<td class='buttons'></td>"
|
||||
}
|
||||
|
||||
html += "<td class='buttons'><span class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></span></td>"
|
||||
html += "</tr>"
|
||||
|
||||
row_num++
|
||||
})
|
||||
|
||||
// Inputs
|
||||
if (setting.can_add === true) {
|
||||
html += makeTableInputs(setting)
|
||||
}
|
||||
|
||||
// populate inputs in the table for new values
|
||||
html += makeTableInputs(setting)
|
||||
html += "</table>"
|
||||
|
||||
return html;
|
||||
|
@ -333,35 +304,43 @@ function makeTable(setting, setting_name, setting_value) {
|
|||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
}
|
||||
html += "<td class='key' name='" + setting.key.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (_.has(setting.key, 'placeholder') ? setting.key.placeholder : "") + "' value=''>\
|
||||
</td>"
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key' name='" + setting.key.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (_.has(setting.key, 'placeholder') ? setting.key.placeholder : "") + "' value=''>\
|
||||
</td>"
|
||||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='row-data'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + col.placeholder + "' value=''>\
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.key ? col.key : "") + "' value=''>\
|
||||
</td>"
|
||||
})
|
||||
html += "<td class='buttons'><span class='glyphicon glyphicon-ok add-row'></span></td>"
|
||||
|
||||
html += "<td class='buttons'><span class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></span></td>"
|
||||
html += "</tr>"
|
||||
|
||||
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("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
} 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("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
// 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',
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)");
|
||||
|
|
249
examples/gamepad.js
Normal file
249
examples/gamepad.js
Normal file
|
@ -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);
|
|
@ -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;
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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") {
|
||||
|
|
|
@ -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);
|
|
@ -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)
|
||||
|
|
32
interface/resources/shaders/metavoxel_voxel_cursor.frag
Normal file
32
interface/resources/shaders/metavoxel_voxel_cursor.frag
Normal file
|
@ -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);
|
||||
}
|
25
interface/resources/shaders/metavoxel_voxel_cursor.vert
Normal file
25
interface/resources/shaders/metavoxel_voxel_cursor.vert
Normal file
|
@ -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;
|
||||
}
|
|
@ -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<FaceTracker*>(&_dde) :
|
||||
(_cara.isActive() ? static_cast<FaceTracker*>(&_cara) :
|
||||
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
|
||||
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
|
||||
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)))));
|
||||
(_visage.isActive() ? static_cast<FaceTracker*>(&_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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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...";
|
||||
|
|
|
@ -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<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute(), QVector<AttributePointer>(), lod),
|
||||
intersectionDistance(FLT_MAX) {
|
||||
}
|
||||
|
||||
int RayVoxelIntersectionVisitor::visit(MetavoxelInfo& info, float distance) {
|
||||
if (!info.isLeaf) {
|
||||
return _order;
|
||||
}
|
||||
const VoxelBuffer* buffer = static_cast<VoxelBuffer*>(
|
||||
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()),
|
||||
CursorRenderVisitor::CursorRenderVisitor(const AttributePointer& attribute, const Box& bounds) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << 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<VoxelPoint>& vertices, const QVector<int>& indices, const QVector<glm::vec3>& hermite,
|
||||
const QVector<SharedObjectPointer>& materials) :
|
||||
const QMultiHash<QRgb, int>& quadIndices, int size, const QVector<SharedObjectPointer>& 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<VoxelPoint>& vertices, const QVector<int>
|
|||
_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<QRgb, int>::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<VoxelPoint> vertices;
|
||||
QVector<int> indices;
|
||||
QVector<glm::vec3> hermiteSegments;
|
||||
QMultiHash<QRgb, int> 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<SharedObjectPointer>());
|
||||
}
|
||||
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 };
|
||||
|
|
|
@ -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<VoxelPoint>& vertices, const QVector<int>& indices, const QVector<glm::vec3>& hermite,
|
||||
const QVector<SharedObjectPointer>& materials = QVector<SharedObjectPointer>());
|
||||
const QMultiHash<QRgb, int>& quadIndices, int size, const QVector<SharedObjectPointer>& materials =
|
||||
QVector<SharedObjectPointer>());
|
||||
|
||||
/// 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<VoxelPoint> _vertices;
|
||||
QVector<int> _indices;
|
||||
QVector<glm::vec3> _hermite;
|
||||
QMultiHash<QRgb, int> _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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <GLMHelpers.h>
|
||||
|
||||
//qt
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <QUdpSocket>
|
||||
|
||||
#include <SimpleMovingAverage.h>
|
||||
#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
|
|
@ -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<float>(SDL_JoystickNumAxes(sdlJoystick))),
|
||||
_buttons(QVector<bool>(SDL_JoystickNumButtons(sdlJoystick))),
|
||||
_sdlJoystick(sdlJoystick)
|
||||
_sdlGameController(sdlGameController),
|
||||
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
|
||||
_axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))),
|
||||
_buttons(QVector<bool>(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<short>::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
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
#include <qobject.h>
|
||||
#include <qvector.h>
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#ifdef HAVE_SDL2
|
||||
#include <SDL.h>
|
||||
#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<float>& getAxes() const { return _axes; }
|
||||
const QVector<bool>& 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<float> _axes;
|
||||
QVector<bool> _buttons;
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
SDL_Joystick* _sdlJoystick;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // hifi_JoystickTracker_h
|
||||
#endif // hifi_JoystickTracker_h
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
220
interface/src/gpu/Resource.cpp
Normal file
220
interface/src/gpu/Resource.cpp
Normal file
|
@ -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 <QDebug>
|
||||
|
||||
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();
|
||||
//}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
166
interface/src/gpu/Resource.h
Normal file
166
interface/src/gpu/Resource.h
Normal file
|
@ -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 <assert.h>
|
||||
#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
|
|
@ -10,8 +10,9 @@
|
|||
//
|
||||
|
||||
#include <QtDebug>
|
||||
#include <QScriptValue>
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#ifdef HAVE_SDL2
|
||||
#include <SDL.h>
|
||||
#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<Joystick*> 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<SDL_JoystickID, Joystick*>::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
|
||||
}
|
||||
|
|
|
@ -15,34 +15,100 @@
|
|||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
#include <SDL.h>
|
||||
#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<QString, Joystick*> _openJoysticks;
|
||||
QStringList _availableDeviceNames;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
QMap<SDL_JoystickID, Joystick*> _openJoysticks;
|
||||
#endif
|
||||
bool _isInitialized;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<QWheelEvent*>(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<QMouseEvent*>(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<MaterialObject*>(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<MaterialObject*>(_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());
|
||||
}
|
||||
|
|
|
@ -469,4 +469,53 @@ private:
|
|||
QSharedPointer<NetworkTexture> _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<NetworkTexture> _texture;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelEditor_h
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
#include "InterfaceConfig.h"
|
||||
#include "TextRenderer.h"
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
|
||||
// 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::Properties, TextRenderer*> TextRenderer::_instances;
|
||||
|
||||
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
|
||||
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <QImage>
|
||||
#include <QVector>
|
||||
|
||||
#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<Properties, TextRenderer*> _instances;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Texture> _billboardTexture;
|
||||
bool _newTextureNeeded;
|
||||
|
||||
QRect _fromImage; // where from in the image to sample
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <QGLWidget>
|
||||
#include <SharedUtil.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "InterfaceConfig.h"
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <PlaneShape.h>
|
||||
#include <RayIntersectionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
|
@ -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<float>::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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ public:
|
|||
|
||||
AACube getMaximumAACubeInTreeUnits() const;
|
||||
AACube getMaximumAACubeInMeters() const;
|
||||
AABox getAABoxInMeters() const;
|
||||
|
||||
void debugDump() const;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<AttributePointer>() << AttributeRegistry::getInstance()->getVoxelColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector<AttributePointer>() <<
|
||||
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<VoxelColorDataPointer>();
|
||||
VoxelMaterialDataPointer materialPointer = info.inputValues.at(1).getInlineValue<VoxelMaterialDataPointer>();
|
||||
if (!(colorPointer && materialPointer && colorPointer->getSize() == materialPointer->getSize())) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
QVector<QRgb> colorContents = colorPointer->getContents();
|
||||
QByteArray materialContents = materialPointer->getContents();
|
||||
QVector<SharedObjectPointer> 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<VoxelColorDataPointer>(newColorPointer));
|
||||
|
||||
clearUnusedMaterials(materials, materialContents);
|
||||
VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, size, materials));
|
||||
info.outputValues[1] = AttributeValue(_inputs.at(1), encodeInline<VoxelMaterialDataPointer>(newMaterialPointer));
|
||||
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
void PaintVoxelMaterialEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
PaintVoxelMaterialEditVisitor visitor(position, radius, material, averageColor);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue