Merge branch 'master' of github.com:highfidelity/hifi into entity-tools

This commit is contained in:
Ryan Huffman 2014-10-13 21:25:47 -07:00
commit f178de3b2e
55 changed files with 3434 additions and 1206 deletions

View 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)

View file

@ -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",

View file

@ -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;
}

View file

@ -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',

View file

@ -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();
}
}

View file

@ -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
View 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);

View file

@ -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

View file

@ -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") {

View file

@ -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);

View file

@ -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)

View 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);
}

View 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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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)
{

View file

@ -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;
};

View file

@ -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");

View file

@ -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...";

View file

@ -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 };

View file

@ -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.

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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;

View 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();
//}
}
};
};

View 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

View file

@ -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
}

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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());
}

View file

@ -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

View file

@ -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,

View file

@ -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) {
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -101,6 +101,7 @@ public:
AACube getMaximumAACubeInTreeUnits() const;
AACube getMaximumAACubeInMeters() const;
AABox getAABoxInMeters() const;
void debugDump() const;

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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:

View file

@ -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);
}

View file

@ -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);