Merge branch 'master' of https://github.com/highfidelity/hifi into entityScriptsAndEvents

Conflicts:
	libraries/entities-renderer/src/EntityTreeRenderer.cpp
	libraries/entities-renderer/src/EntityTreeRenderer.h
This commit is contained in:
ZappoMan 2014-12-19 10:35:18 -08:00
commit cfa5e5c61b
358 changed files with 14157 additions and 16708 deletions
.gitignoreBUILD.mdBUILD_LINUX.mdBUILD_OSX.mdBUILD_WIN.mdCMakeLists.txt
assignment-client
cmake
domain-server
examples
interface

4
.gitignore vendored
View file

@ -32,5 +32,9 @@ DerivedData
interface/external/*/*
!interface/external/*/readme.txt
# ignore interface optional resources
interface/resources/visage/*
!interface/resources/visage/tracker.cfg
# Ignore interfaceCache for Linux users
interface/interfaceCache/

230
BUILD.md
View file

@ -1,33 +1,29 @@
Dependencies
===
###Dependencies
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.12.2
* [Qt](http://qt-project.org/downloads) ~> 5.2.0
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.2
* [Qt](http://qt-project.org/downloads) ~> 5.3.0
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g
* IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability.
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
#####Linux only
* [freeglut](http://freeglut.sourceforge.net/) ~> 2.8.0
* [zLib](http://www.zlib.net/) ~> 1.2.8
### OS Specific Build Guides
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
#####Windows only
* [GLEW](http://glew.sourceforge.net/) ~> 1.10.0
* [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1
* [zLib](http://www.zlib.net/) ~> 1.2.8
CMake
===
###CMake
Hifi uses CMake to generate build files and project files for your platform.
####Qt
In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation.
For example, a Qt5 5.2.0 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
For example, a Qt5 5.3.2 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.2.1/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/clang_64/lib/cmake/
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.3.2/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
####Generating build files
@ -42,7 +38,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.1/lib/cmake
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.3.2/lib/cmake
####Finding Dependencies
You can point our [Cmake find modules](cmake/modules/) to the correct version of dependencies by setting one of the three following variables to the location of the correct version of the dependency.
@ -53,201 +49,7 @@ In the examples below the variable $NAME would be replaced by the name of the de
* $NAME_ROOT_DIR - set this variable in your ENV
* HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name'
UNIX
===
In general, as long as external dependencies are placed in OS standard locations, CMake will successfully find them during its run. When possible, you may choose to install depencies from your package manager of choice, or from source.
####Linux
Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
####OS X
#####Package Managers
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple.
brew tap highfidelity/homebrew-formulas
brew install cmake glm openssl
brew install highfidelity/formulas/qt5
brew link qt5 --force
We have a [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas) that you can use/tap to install some of the dependencies. In the code block above qt5 is installed from a formula in this repository.
*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.2.0 stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.*
#####Xcode
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
cmake .. -GXcode
After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.
If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories.
Windows
===
####Visual Studio
Currently building on Windows has been tested using the following compilers:
* Visual Studio C++ 2010 Express
* Visual Studio 2013
(If anyone can test using Visual Studio 2013 Express then please update this document)
#####Windows SDK 7.1
Whichever version of Visual Studio you use, you will need [Microsoft Windows SDK for Windows 7 and .NET Framework 4](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
NOTE: If using Visual Studio C++ 2010 Express, you need to follow a specific install order. See below before installing the Windows SDK.
######Windows 8.1
You may have already downloaded the Windows 8 SDK (e.g. if you have previously installed Visual Studio 2013). If so, change CMAKE_PREFIX_PATH in %HIFI_DIR%\CMakeLists.txt to point to the Windows 8 SDK binaries. The default path is `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86`
#####Visual Studio C++ 2010 Express
Visual Studio C++ 2010 Express can be downloaded [here](http://www.visualstudio.com/en-us/downloads#d-2010-express).
The following patches/service packs are also required:
* [VS2010 SP1](http://www.microsoft.com/en-us/download/details.aspx?id=23691)
* [VS2010 SP1 Compiler Update](http://www.microsoft.com/en-us/download/details.aspx?id=4422)
IMPORTANT: Use the following install order:
Visual Studio C++ 2010 Express
Windows SDK 7.1
VS2010 SP1
VS2010 SP1 Compiler Update
If you get an error while installing the VS2010 SP1 Compiler update saying that you don't have the Windows SDK installed, then uninstall all of the above and start again in the correct order.
Some of the build instructions will ask you to start a Visual Studio Command Prompt. You should have a shortcut in your Start menu called "Open Visual Studio Command Prompt (2010)" which will do so.
#####Visual Studio 2013
This product must be purchased separately.
Visual Studio 2013 doesn't have a shortcut to start a Visual Studio Command Prompt. Instead, start a regular command prompt and then run:
"%VS120COMNTOOLS%\vsvars32.bat"
If you experience issues building interface on Visual Studio 2013, try generating the build files with Visual Studio 2010 instead. To do so, download Visual Studio 2010 and run `cmake .. -G "Visual Studio 10"` (Assuming running from %HIFI_DIR%\build).
####Qt
You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version.
NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules.
* Download the online installer [here](http://qt-project.org/downloads)
* When it asks you to select components, ONLY select the following:
* Qt > Qt 5.2.0 > **msvc2010 32-bit OpenGL**
* Download the offline installer [here](http://download.qt-project.org/official_releases/qt/5.2/5.2.0/qt-windows-opensource-5.2.0-msvc2010_opengl-x86-offline.exe)
Once Qt is installed, you need to manually configure the following:
* Make sure the Qt runtime DLLs are loadable. You must do this before you attempt to build because some tools for the build depend on Qt. E.g., add to the PATH: `Qt\5.2.0\msvc2010_opengl\bin\`.
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.2.0\msvc2010_opengl` directory.
####External Libraries
CMake will need to know where the headers and libraries for required external dependencies are.
The recommended route for CMake to find the external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure:
root_lib_dir
-> freeglut
-> bin
-> include
-> lib
-> glew
-> bin
-> include
-> lib
-> glm
-> glm
-> glm.hpp
-> openssl
-> bin
-> include
-> lib
-> zlib
-> include
-> lib
-> test
For many of the external libraries where precompiled binaries are readily available you should be able to simply copy the extracted folder that you get from the download links provided at the top of the guide. Otherwise you may need to build from source and install the built product to this directory. The `root_lib_dir` in the above example can be wherever you choose on your system - as long as the environment variable HIFI_LIB_DIR is set to it. From here on, whenever you see %HIFI_LIB_DIR% you should substitute the directory that you chose.
As with the Qt libraries, you will need to make sure that directories containing DLL'S are in your path. Where possible, you can use static builds of the external dependencies to avoid this requirement.
#### OpenSSL
QT will use OpenSSL if it's available, but it doesn't install it, so you must install it separately.
Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll, libeay32.dll) lying around, but they may be the wrong version. If these DLL's are in the PATH then QT will try to use them, and if they're the wrong version then you will see the following errors in the console:
QSslSocket: cannot resolve TLSv1_1_client_method
QSslSocket: cannot resolve TLSv1_2_client_method
QSslSocket: cannot resolve TLSv1_1_server_method
QSslSocket: cannot resolve TLSv1_2_server_method
QSslSocket: cannot resolve SSL_select_next_proto
QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
QSslSocket: cannot resolve SSL_get0_next_proto_negotiated
To prevent these problems, install OpenSSL yourself. Download the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html):
* Visual C++ 2008 Redistributables
* Win32 OpenSSL v1.0.1h
Install OpenSSL into the Windows system directory, to make sure that QT uses the version that you've just installed, and not some other version.
#### Zlib
Download the compiled DLL from the [zlib website](http://www.zlib.net/). Extract to %HIFI_LIB_DIR%\zlib.
Add the following environment variables (remember to substitute your own directory for %HIFI_LIB_DIR%):
ZLIB_LIBRARY=%HIFI_LIB_DIR%\zlib\lib\zdll.lib
ZLIB_INCLUDE_DIR=%HIFI_LIB_DIR%\zlib\include
Add to the PATH: `%HIFI_LIB_DIR%\zlib`
Important! This should be added at the beginning of the path, not the end. That's because your
system likely has many copies of zlib1.dll, and you want High Fidelity to use the correct version. If High Fidelity picks up the wrong zlib1.dll then it might be unable to use it, and that would cause it to fail to start, showing only the cryptic error "The application was unable to start correctly: 0xc0000022".
#### freeglut
Download the binary package: `freeglut-MSVC-2.8.1-1.mp.zip`. Extract to %HIFI_LIB_DIR%\freeglut.
Add to the PATH: `%HIFI_LIB_DIR%\freeglut\bin`
#### GLEW
Download the binary package: `glew-1.10.0-win32.zip`. Extract to %HIFI_LIB_DIR%\glew (you'll need to rename the default directory name).
Add to the PATH: `%HIFI_LIB_DIR%\glew\bin\Release\Win32`
#### GLM
This package contains only headers, so there's nothing to add to the PATH.
Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.
#### Build High Fidelity using Visual Studio
Follow the same build steps from the CMake section, but pass a different generator to CMake.
cmake .. -DZLIB_LIBRARY=%ZLIB_LIBRARY% -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% -G "Visual Studio 10"
If you're using Visual Studio 2013 then pass "Visual Studio 12" instead of "Visual Studio 10" (yes, 12, not 13).
Open %HIFI_DIR%\build\hifi.sln and compile.
####Running Interface
If you need to debug Interface, you can run interface from within Visual Studio (see the section below). You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Debug\interface.exe
####Debugging Interface
* In the Solution Explorer, right click interface and click Set as StartUp Project
* Set the "Working Directory" for the Interface debugging sessions to the Debug output directory so that your application can load resources. Do this: right click interface and click Properties, choose Debugging from Configuration Properties, set Working Directory to .\Debug
* Now you can run and debug interface through Visual Studio
Optional Components
===
###Optional Components
####QXmpp
@ -255,7 +57,7 @@ You can find QXmpp [here](https://github.com/qxmpp-project/qxmpp). The inclusion
OS X users who tap our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas) can install QXmpp via homebrew - `brew install highfidelity/formulas/qxmpp`.
#### Devices
####Devices
You can support external input/output devices such as Leap Motion, Faceplus, Faceshift, PrioVR, MIDI, Razr Hydra and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device.

12
BUILD_LINUX.md Normal file
View file

@ -0,0 +1,12 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
###Linux Specific Dependencies
* [freeglut](http://freeglut.sourceforge.net/) ~> 2.8.0
* [zLib](http://www.zlib.net/) ~> 1.2.8
In general, as long as external dependencies are placed in OS standard locations, CMake will successfully find them during its run. When possible, you may choose to install depencies from your package manager of choice, or from source.
###Qt5 Dependencies
Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev

22
BUILD_OSX.md Normal file
View file

@ -0,0 +1,22 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
###Homebrew
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple.
brew tap highfidelity/homebrew-formulas
brew install cmake glm openssl tbb
brew install highfidelity/formulas/qt5
brew link qt5 --force
We have a [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas) that you can use/tap to install some of the dependencies. In the code block above qt5 is installed from a formula in this repository.
*Our [qt5 homebrew formula](https://raw.github.com/highfidelity/homebrew-formulas/master/qt5.rb) is for a patched version of Qt 5.3.x stable that removes wireless network scanning that can reduce real-time audio performance. We recommended you use this formula to install Qt.*
###Xcode
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
cmake .. -GXcode
After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.
If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories.

149
BUILD_WIN.md Normal file
View file

@ -0,0 +1,149 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Windows specific instructions are found in this file.
###Windows Dependencies
* [GLEW](http://glew.sourceforge.net/) ~> 1.10.0
* [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1
* [zLib](http://www.zlib.net/) ~> 1.2.8
###Visual Studio
Currently building on Windows has been tested using the following compilers:
* Visual Studio 2013
(If anyone can test using Visual Studio 2013 Express then please update this document)
####Visual Studio 2013
You can use the Community or Professional editions of Visual Studio 2013.
You can start a Visual Studio 2013 command prompt using the shortcut provided in the Visual Studio Tools folder installed as part of Visual Studio 2013.
Or you can start a regular command prompt and then run:
"%VS120COMNTOOLS%\vsvars32.bat"
#####Windows SDK 8.1
If using Visual Studio 2013 and building as a Visual Studio 2013 project you need the Windows 8 SDK which you should already have as part of installing Visual Studio 2013. You should be able to see it at `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86`.
###Qt
You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version.
NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules.
* Download the online installer [here](http://qt-project.org/downloads)
* When it asks you to select components, ONLY select the following:
* Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL**
* Download the offline installer [here](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe)
Once Qt is installed, you need to manually configure the following:
* Make sure the Qt runtime DLLs are loadable. You must do this before you attempt to build because some tools for the build depend on Qt. E.g., add to the PATH: `Qt\5.3.2\msvc2013_opengl\bin\`.
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl` directory.
###External Libraries
CMake will need to know where the headers and libraries for required external dependencies are.
The recommended route for CMake to find the external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure:
root_lib_dir
-> freeglut
-> bin
-> include
-> lib
-> glew
-> bin
-> include
-> lib
-> glm
-> glm
-> glm.hpp
-> openssl
-> bin
-> include
-> lib
-> tbb
-> include
-> lib
-> zlib
-> include
-> lib
-> test
For many of the external libraries where precompiled binaries are readily available you should be able to simply copy the extracted folder that you get from the download links provided at the top of the guide. Otherwise you may need to build from source and install the built product to this directory. The `root_lib_dir` in the above example can be wherever you choose on your system - as long as the environment variable HIFI_LIB_DIR is set to it. From here on, whenever you see %HIFI_LIB_DIR% you should substitute the directory that you chose.
As with the Qt libraries, you will need to make sure that directories containing DLL'S are in your path. Where possible, you can use static builds of the external dependencies to avoid this requirement.
###OpenSSL
QT will use OpenSSL if it's available, but it doesn't install it, so you must install it separately.
Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll, libeay32.dll) lying around, but they may be the wrong version. If these DLL's are in the PATH then QT will try to use them, and if they're the wrong version then you will see the following errors in the console:
QSslSocket: cannot resolve TLSv1_1_client_method
QSslSocket: cannot resolve TLSv1_2_client_method
QSslSocket: cannot resolve TLSv1_1_server_method
QSslSocket: cannot resolve TLSv1_2_server_method
QSslSocket: cannot resolve SSL_select_next_proto
QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
QSslSocket: cannot resolve SSL_get0_next_proto_negotiated
To prevent these problems, install OpenSSL yourself. Download the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html):
* Visual C++ 2008 Redistributables
* Win32 OpenSSL v1.0.1h
Install OpenSSL into the Windows system directory, to make sure that QT uses the version that you've just installed, and not some other version.
###Intel Threading Building Blocks (TBB)
Download the zip from the [TBB website](https://www.threadingbuildingblocks.org/).
We recommend you extract it to %HIFI_LIB_DIR%\tbb. This will help our FindTBB cmake module find what it needs. You can place it wherever you like on your machine if you specify TBB_ROOT_DIR as an environment variable or a variable passed when cmake is run.
###Zlib
Download the compiled DLL from the [zlib website](http://www.zlib.net/). Extract to %HIFI_LIB_DIR%\zlib.
Add the following environment variables (remember to substitute your own directory for %HIFI_LIB_DIR%):
ZLIB_LIBRARY=%HIFI_LIB_DIR%\zlib\lib\zdll.lib
ZLIB_INCLUDE_DIR=%HIFI_LIB_DIR%\zlib\include
Add to the PATH: `%HIFI_LIB_DIR%\zlib`
Important! This should be added at the beginning of the path, not the end. That's because your
system likely has many copies of zlib1.dll, and you want High Fidelity to use the correct version. If High Fidelity picks up the wrong zlib1.dll then it might be unable to use it, and that would cause it to fail to start, showing only the cryptic error "The application was unable to start correctly: 0xc0000022".
###freeglut
Download the binary package: `freeglut-MSVC-2.8.1-1.mp.zip`. Extract to %HIFI_LIB_DIR%\freeglut.
Add to the PATH: `%HIFI_LIB_DIR%\freeglut\bin`
###GLEW
Download the binary package: `glew-1.10.0-win32.zip`. Extract to %HIFI_LIB_DIR%\glew (you'll need to rename the default directory name).
Add to the PATH: `%HIFI_LIB_DIR%\glew\bin\Release\Win32`
###GLM
This package contains only headers, so there's nothing to add to the PATH.
Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.
###Build High Fidelity using Visual Studio
Follow the same build steps from the CMake section, but pass a different generator to CMake.
cmake .. -DZLIB_LIBRARY=%ZLIB_LIBRARY% -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% -G "Visual Studio 12"
Open %HIFI_DIR%\build\hifi.sln and compile.
###Running Interface
If you need to debug Interface, you can run interface from within Visual Studio (see the section below). You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Debug\interface.exe
###Debugging Interface
* In the Solution Explorer, right click interface and click Set as StartUp Project
* Set the "Working Directory" for the Interface debugging sessions to the Debug output directory so that your application can load resources. Do this: right click interface and click Properties, choose Debugging from Configuration Properties, set Working Directory to .\Debug
* Now you can run and debug interface through Visual Studio

View file

@ -34,6 +34,24 @@ elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing")
endif(WIN32)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if (COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
if (APPLE)
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++0x")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++")
endif ()
if (NOT QT_CMAKE_PREFIX_PATH)
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
endif ()

View file

@ -12,7 +12,7 @@ link_hifi_libraries(
)
if (UNIX)
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK ${CMAKE_DL_LIBS})
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
endif (UNIX)
link_shared_dependencies()

View file

@ -10,6 +10,7 @@
//
#include <QtCore/QProcess>
#include <QtCore/qsharedmemory.h>
#include <QtCore/QThread>
#include <QtCore/QTimer>
@ -21,6 +22,7 @@
#include <NodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ShutdownEventListener.h>
#include <SoundCache.h>
#include "AssignmentFactory.h"
@ -37,8 +39,8 @@ int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
AssignmentClient::AssignmentClient(int &argc, char **argv) :
QCoreApplication(argc, argv),
_shutdownEventListener(this),
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME)
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME),
_localASPortSharedMem(NULL)
{
LogUtils::init();
@ -47,8 +49,12 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
setApplicationName("assignment-client");
QSettings::setDefaultFormat(QSettings::IniFormat);
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
installNativeEventFilter(&ShutdownEventListener::getInstance());
#else
ShutdownEventListener::getInstance();
#endif
// set the logging target to the the CHILD_TARGET_NAME
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
@ -89,13 +95,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
// create a NodeList as an unassigned client
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
unsigned short assignmentServerPort = DEFAULT_DOMAIN_SERVER_PORT;
// check for an overriden assignment server port
if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION)) {
assignmentServerPort =
argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
}
quint16 assignmentServerPort = DEFAULT_DOMAIN_SERVER_PORT;
// check for an overriden assignment server hostname
if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) {
@ -103,10 +103,16 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
_assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString();
}
HifiSockAddr assignmentServerSocket(_assignmentServerHostname, assignmentServerPort, true);
nodeList->setAssignmentServerSocket(assignmentServerSocket);
// check for an overriden assignment server port
if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION)) {
assignmentServerPort =
argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
}
_assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true);
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
qDebug() << "Assignment server socket is" << assignmentServerSocket;
qDebug() << "Assignment server socket is" << _assignmentServerSocket;
// call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required
qDebug() << "Waiting for assignment -" << _requestAssignment;
@ -129,7 +135,40 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
void AssignmentClient::sendAssignmentRequest() {
if (!_currentAssignment) {
NodeList::getInstance()->sendAssignment(_requestAssignment);
NodeList* nodeList = NodeList::getInstance();
if (_assignmentServerHostname == "localhost") {
// we want to check again for the local domain-server port in case the DS has restarted
if (!_localASPortSharedMem) {
_localASPortSharedMem = new QSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this);
if (!_localASPortSharedMem->attach(QSharedMemory::ReadOnly)) {
qWarning() << "Could not attach to shared memory at key" << DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY
<< "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort();
}
}
if (_localASPortSharedMem->isAttached()) {
_localASPortSharedMem->lock();
quint16 localAssignmentServerPort;
memcpy(&localAssignmentServerPort, _localASPortSharedMem->data(), sizeof(localAssignmentServerPort));
_localASPortSharedMem->unlock();
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
qDebug() << "Port for local assignment server read from shared memory is"
<< localAssignmentServerPort;
_assignmentServerSocket.setPort(localAssignmentServerPort);
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
}
}
}
nodeList->sendAssignment(_requestAssignment);
}
}

View file

@ -14,9 +14,10 @@
#include <QtCore/QCoreApplication>
#include "ShutdownEventListener.h"
#include "ThreadedAssignment.h"
class QSharedMemory;
class AssignmentClient : public QCoreApplication {
Q_OBJECT
public:
@ -32,8 +33,9 @@ private slots:
private:
Assignment _requestAssignment;
static SharedAssignmentPointer _currentAssignment;
ShutdownEventListener _shutdownEventListener;
QString _assignmentServerHostname;
HifiSockAddr _assignmentServerSocket;
QSharedMemory* _localASPortSharedMem;
};
#endif // hifi_AssignmentClient_h

View file

@ -12,6 +12,7 @@
#include <signal.h>
#include <LogHandler.h>
#include <ShutdownEventListener.h>
#include "AssignmentClientMonitor.h"
@ -19,24 +20,19 @@ const char* NUM_FORKS_PARAMETER = "-n";
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
void signalHandler(int param){
// get the qApp and cast it to an AssignmentClientMonitor
AssignmentClientMonitor* app = qobject_cast<AssignmentClientMonitor*>(qApp);
// tell it to stop the child processes and then go down
app->stopChildProcesses();
app->quit();
}
AssignmentClientMonitor::AssignmentClientMonitor(int &argc, char **argv, int numAssignmentClientForks) :
QCoreApplication(argc, argv)
{
// be a signal handler for SIGTERM so we can stop our children when we get it
signal(SIGTERM, signalHandler);
{
// start the Logging class with the parent's target name
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME);
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
installNativeEventFilter(&ShutdownEventListener::getInstance());
#else
ShutdownEventListener::getInstance();
#endif
_childArguments = arguments();
// remove the parameter for the number of forks so it isn't passed to the child forked processes
@ -52,6 +48,10 @@ AssignmentClientMonitor::AssignmentClientMonitor(int &argc, char **argv, int num
}
}
AssignmentClientMonitor::~AssignmentClientMonitor() {
stopChildProcesses();
}
void AssignmentClientMonitor::stopChildProcesses() {
QList<QPointer<QProcess> >::Iterator it = _childProcesses.begin();

View file

@ -24,6 +24,7 @@ class AssignmentClientMonitor : public QCoreApplication {
Q_OBJECT
public:
AssignmentClientMonitor(int &argc, char **argv, int numAssignmentClientForks);
~AssignmentClientMonitor();
void stopChildProcesses();
private slots:

View file

@ -439,12 +439,13 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
// loop through all other nodes that have sufficient audio to mix
int streamsMixed = 0;
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([&](const SharedNodePointer& otherNode){
if (otherNode->getLinkedData()) {
AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData();
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
const QHash<QUuid, PositionalAudioStream*>& otherNodeAudioStreams = otherNodeClientData->getAudioStreams();
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
for (i = otherNodeAudioStreams.constBegin(); i != otherNodeAudioStreams.constEnd(); i++) {
@ -454,14 +455,15 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
if (otherNodeStream->getType() == PositionalAudioStream::Microphone) {
streamUUID = otherNode->getUUID();
}
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID,
otherNodeStream, nodeAudioStream);
streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID,
otherNodeStream, nodeAudioStream);
}
}
}
}
});
return streamsMixed;
}
@ -539,12 +541,11 @@ void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const Hif
QByteArray packet = receivedPacket;
populatePacketHeader(packet, PacketTypeMuteEnvironment);
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& node){
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) {
nodeList->writeDatagram(packet, packet.size(), node);
}
}
});
} else {
// let processNodeData handle it.
nodeList->processNodeData(senderSockAddr, receivedPacket);
@ -607,8 +608,9 @@ void AudioMixer::sendStatsPacket() {
NodeList* nodeList = NodeList::getInstance();
int clientNumber = 0;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& node) {
// if we're too large, send the packet
if (sizeOfStats > TOO_BIG_FOR_MTU) {
nodeList->sendStatsToDomainServer(statsObject2);
@ -626,7 +628,7 @@ void AudioMixer::sendStatsPacket() {
somethingToSend = true;
sizeOfStats += property.size() + value.size();
}
}
});
if (somethingToSend) {
nodeList->sendStatsToDomainServer(statsObject2);
@ -764,7 +766,8 @@ void AudioMixer::run() {
_lastPerSecondCallbackTime = now;
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& node) {
if (node->getLinkedData()) {
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
@ -831,7 +834,7 @@ void AudioMixer::run() {
++_sumListeners;
}
}
}
});
++_numStatFrames;
@ -889,7 +892,7 @@ void AudioMixer::perSecondActions() {
_timeSpentPerHashMatchCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0,
_timeSpentPerHashMatchCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0);
foreach(const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([](const SharedNodePointer& node) {
if (node->getLinkedData()) {
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
@ -899,7 +902,7 @@ void AudioMixer::perSecondActions() {
nodeData->printUpstreamDownstreamStats();
}
}
}
});
}
_datagramsReadPerCallStats.currentIntervalComplete();

View file

@ -122,7 +122,7 @@ void AvatarMixer::broadcastAvatarData() {
AvatarMixerClientData* nodeData = NULL;
AvatarMixerClientData* otherNodeData = NULL;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& node) {
if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()
&& (nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()))->getMutex().tryLock()) {
++_sumListeners;
@ -135,7 +135,7 @@ void AvatarMixer::broadcastAvatarData() {
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& otherNode) {
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()
&& (otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()))->getMutex().tryLock()) {
@ -203,13 +203,13 @@ void AvatarMixer::broadcastAvatarData() {
otherNodeData->getMutex().unlock();
}
}
});
nodeList->writeDatagram(mixedAvatarByteArray, node);
nodeData->getMutex().unlock();
}
}
});
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
}

View file

@ -28,7 +28,7 @@ void ScriptableAvatar::startAnimation(const QString& url, float fps, float prior
Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints));
return;
}
_animation = _scriptEngine->getAnimationCache()->getAnimation(url);
_animation = DependencyManager::get<AnimationCache>()->getAnimation(url);
_animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame);
_maskedJoints = maskedJoints;
}

View file

@ -11,6 +11,7 @@
#include <QTimer>
#include <EntityTree.h>
#include <SimpleEntitySimulation.h>
#include "EntityServer.h"
#include "EntityServerConsts.h"
@ -20,7 +21,8 @@ const char* MODEL_SERVER_NAME = "Entity";
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server";
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
EntityServer::EntityServer(const QByteArray& packet) : OctreeServer(packet) {
EntityServer::EntityServer(const QByteArray& packet)
: OctreeServer(packet), _entitySimulation(NULL) {
// nothing special to do here...
}
@ -36,6 +38,12 @@ OctreeQueryNode* EntityServer::createOctreeQueryNode() {
Octree* EntityServer::createTree() {
EntityTree* tree = new EntityTree(true);
tree->addNewlyCreatedHook(this);
if (!_entitySimulation) {
SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation();
simpleSimulation->setEntityTree(tree);
tree->setSimulation(simpleSimulation);
_entitySimulation = simpleSimulation;
}
return tree;
}
@ -123,15 +131,17 @@ void EntityServer::pruneDeletedEntities() {
if (tree->hasAnyDeletedEntities()) {
quint64 earliestLastDeletedEntitiesSent = usecTimestampNow() + 1; // in the future
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
if (otherNode->getLinkedData()) {
EntityNodeData* nodeData = static_cast<EntityNodeData*>(otherNode->getLinkedData());
NodeList::getInstance()->eachNode([&earliestLastDeletedEntitiesSent](const SharedNodePointer& node) {
if (node->getLinkedData()) {
EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
quint64 nodeLastDeletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
if (nodeLastDeletedEntitiesSentAt < earliestLastDeletedEntitiesSent) {
earliestLastDeletedEntitiesSent = nodeLastDeletedEntitiesSentAt;
}
}
}
});
tree->forgetEntitiesDeletedBefore(earliestLastDeletedEntitiesSent);
}
}

View file

@ -27,7 +27,6 @@ public:
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode();
virtual Octree* createTree();
virtual char getMyNodeType() const { return NodeType::EntityServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; }
@ -46,7 +45,11 @@ public:
public slots:
void pruneDeletedEntities();
protected:
virtual Octree* createTree();
private:
EntitySimulation* _entitySimulation;
};
#endif // hifi_EntityServer_h

View file

@ -311,7 +311,7 @@ MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) :
const char* SAVE_FILE = "/resources/metavoxels.dat";
const int FILE_MAGIC = 0xDADAFACE;
const int FILE_VERSION = 1;
const int FILE_VERSION = 2;
void MetavoxelPersister::load() {
QString path = QCoreApplication::applicationDirPath() + SAVE_FILE;

View file

@ -261,7 +261,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
continue;
}
const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
const SharedNodePointer& destinationNode = NodeList::getInstance()->nodeWithUUID(nodeUUID);
// retrieve sequence number stats of node, prune its missing set
SequenceNumberStats& sequenceNumberStats = nodeStats.getIncomingEditSequenceNumberStats();

View file

@ -1222,13 +1222,16 @@ void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
_octreeInboundPacketProcessor->shuttingDown();
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([this](const SharedNodePointer& node) {
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
forceNodeShutdown(node);
}
});
if (_persistThread) {
_persistThread->aboutToFinish();
}
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";
}

View file

@ -62,7 +62,6 @@ public:
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode() = 0;
virtual Octree* createTree() = 0;
virtual char getMyNodeType() const = 0;
virtual PacketType getMyQueryMessageType() const = 0;
virtual const char* getMyServerName() const = 0;
@ -132,6 +131,7 @@ public slots:
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
protected:
virtual Octree* createTree() = 0;
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result);

View file

@ -36,7 +36,6 @@ public:
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode();
virtual Octree* createTree();
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; }
virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; }
@ -50,6 +49,7 @@ public:
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
protected:
virtual Octree* createTree();
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject);
private:

View file

@ -25,10 +25,9 @@ macro(LINK_HIFI_LIBRARIES)
# link the actual library - it is static so don't bubble it up
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
# ask the library what its dynamic dependencies are and link them
get_target_property(LINKED_TARGET_DEPENDENCY_LIBRARIES ${HIFI_LIBRARY} DEPENDENCY_LIBRARIES)
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK ${LINKED_TARGET_DEPENDENCY_LIBRARIES})
# ask the library what its include dependencies are and link them
get_target_property(LINKED_TARGET_DEPENDENCY_INCLUDES ${HIFI_LIBRARY} DEPENDENCY_INCLUDES)
list(APPEND ${TARGET_NAME}_DEPENDENCY_INCLUDES ${LINKED_TARGET_DEPENDENCY_INCLUDES})
endforeach()
endmacro(LINK_HIFI_LIBRARIES)

View file

@ -9,17 +9,14 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(LINK_SHARED_DEPENDENCIES)
if (${TARGET_NAME}_LIBRARIES_TO_LINK)
list(REMOVE_DUPLICATES ${TARGET_NAME}_LIBRARIES_TO_LINK)
macro(LINK_SHARED_DEPENDENCIES)
if (${TARGET_NAME}_DEPENDENCY_INCLUDES)
list(REMOVE_DUPLICATES ${TARGET_NAME}_DEPENDENCY_INCLUDES)
# link these libraries to our target
target_link_libraries(${TARGET_NAME} ${${TARGET_NAME}_LIBRARIES_TO_LINK})
# include those in our own target
include_directories(SYSTEM ${${TARGET_NAME}_DEPENDENCY_INCLUDES})
endif ()
# we've already linked our Qt modules, but we need to bubble them up to parents
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${${TARGET}_QT_MODULES_TO_LINK}")
# set the property on this target so it can be retreived by targets linking to us
set_target_properties(${TARGET_NAME} PROPERTIES DEPENDENCY_LIBRARIES "${${TARGET}_LIBRARIES_TO_LINK}")
set_target_properties(${TARGET_NAME} PROPERTIES DEPENDENCY_INCLUDES "${${TARGET_NAME}_DEPENDENCY_INCLUDES}")
endmacro(LINK_SHARED_DEPENDENCIES)

View file

@ -18,16 +18,14 @@ macro(SETUP_HIFI_LIBRARY)
# create a library and set the property so it can be referenced later
add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOMTC_SRC})
set(QT_MODULES_TO_LINK ${ARGN})
list(APPEND QT_MODULES_TO_LINK Core)
set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN})
list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
find_package(Qt5 COMPONENTS ${QT_MODULES_TO_LINK} REQUIRED)
foreach(QT_MODULE ${QT_MODULES_TO_LINK})
get_target_property(QT_LIBRARY_LOCATION Qt5::${QT_MODULE} LOCATION)
# add the actual path to the Qt module to our LIBRARIES_TO_LINK variable
# find these Qt modules and link them to our own target
find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
list(APPEND ${TARGET_NAME}_QT_MODULES_TO_LINK ${QT_LIBRARY_LOCATION})
endforeach()
endmacro(SETUP_HIFI_LIBRARY)

View file

@ -25,17 +25,13 @@ macro(SETUP_HIFI_PROJECT)
# add the executable, include additional optional sources
add_executable(${TARGET_NAME} ${TARGET_SRCS} "${AUTOMTC_SRC}")
set(QT_MODULES_TO_LINK ${ARGN})
list(APPEND QT_MODULES_TO_LINK Core)
set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN})
list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
find_package(Qt5 COMPONENTS ${QT_MODULES_TO_LINK} REQUIRED)
foreach(QT_MODULE ${QT_MODULES_TO_LINK})
# find these Qt modules and link them to our own target
find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
# add the actual path to the Qt module to our LIBRARIES_TO_LINK variable
get_target_property(QT_LIBRARY_LOCATION Qt5::${QT_MODULE} LOCATION)
list(APPEND ${TARGET_NAME}_QT_MODULES_TO_LINK ${QT_LIBRARY_LOCATION})
endforeach()
endmacro()

View file

@ -48,8 +48,13 @@ elseif (UNIX)
select_library_configurations(XINERAMA)
elseif (WIN32)
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
if (MSVC10)
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
elseif (MSVC12)
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
endif ()
find_package(ATL)
endif ()

View file

@ -0,0 +1,75 @@
#
# FindTBB.cmake
#
# Try to find the Intel Threading Building Blocks library
#
# You can provide a TBB_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# TBB_FOUND - system was able to find TBB
# TBB_INCLUDE_DIRS - the TBB include directory
# TBB_LIBRARIES - link this to use TBB
#
# Created on 12/14/2014 by Stephen Birarda
# 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("tbb")
find_path(TBB_INCLUDE_DIRS tbb/tbb.h PATH_SUFFIXES include HINTS ${TBB_SEARCH_DIRS})
set(_TBB_LIB_NAME "tbb")
set(_TBB_LIB_MALLOC_NAME "${_TBB_LIB_NAME}malloc")
if (APPLE)
set(_TBB_LIB_DIR "lib/libc++")
elseif (UNIX)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_TBB_ARCH_DIR "intel64")
else()
set(_TBB_ARCH_DIR "ia32")
endif()
execute_process(
COMMAND ${CMAKE_C_COMPILER} -dumpversion
OUTPUT_VARIABLE GCC_VERSION
)
if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4)
set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.4")
elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1)
set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.1")
else ()
message(FATAL_ERROR "Could not find a compatible version of Threading Building Blocks library for your compiler.")
endif ()
elseif (WIN32)
if (CMAKE_CL_64)
set(_TBB_ARCH_DIR "intel64")
else()
set(_TBB_ARCH_DIR "ia32")
endif()
set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/vc12")
endif ()
find_library(TBB_LIBRARY_DEBUG NAMES ${_TBB_LIB_NAME}_debug PATH_SUFFIXES ${_TBB_LIB_DIR} HINTS ${TBB_SEARCH_DIRS})
find_library(TBB_LIBRARY_RELEASE NAMES ${_TBB_LIB_NAME} PATH_SUFFIXES ${_TBB_LIB_DIR} HINTS ${TBB_SEARCH_DIRS})
find_library(TBB_MALLOC_LIBRARY_DEBUG NAMES ${_TBB_LIB_MALLOC_NAME}_debug PATH_SUFFIXES ${_TBB_LIB_DIR} HINTS ${TBB_SEARCH_DIRS})
find_library(TBB_MALLOC_LIBRARY_RELEASE NAMES ${_TBB_LIB_MALLOC_NAME} PATH_SUFFIXES ${_TBB_LIB_DIR} HINTS ${TBB_SEARCH_DIRS})
include(SelectLibraryConfigurations)
include(FindPackageHandleStandardArgs)
select_library_configurations(TBB)
select_library_configurations(TBB_MALLOC)
find_package_handle_standard_args(TBB DEFAULT_MSG TBB_LIBRARY TBB_MALLOC_LIBRARY TBB_INCLUDE_DIRS)
set(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY})

View file

@ -32,9 +32,10 @@ if (APPLE)
find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
find_library(VISAGE_OPENCV_LIBRARY NAME OpenCV PATH_SUFFIXES dependencies/OpenCV_MacOSX/lib HINTS ${VISAGE_SEARCH_DIRS})
find_library(AppKit AppKit)
find_library(QuartzCore QuartzCore)
find_library(CoreVideo CoreVideo)
find_library(QTKit QTKit)
find_library(IOKit IOKit)
elseif (WIN32)
find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h PATH_SUFFIXES dependencies/libxml2/include HINTS ${VISAGE_SEARCH_DIRS})
find_path(VISAGE_OPENCV_INCLUDE_DIR opencv/cv.h PATH_SUFFIXES dependencies/OpenCV/include HINTS ${VISAGE_SEARCH_DIRS})
@ -49,19 +50,19 @@ include(FindPackageHandleStandardArgs)
list(APPEND VISAGE_ARGS_LIST VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR
VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY VISAGE_OPENCV_LIBRARY)
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY)
if (APPLE)
list(APPEND VISAGE_ARGS_LIST QuartzCore AppKit QTKit)
list(APPEND VISAGE_ARGS_LIST CoreVideo QTKit IOKit)
endif ()
find_package_handle_standard_args(Visage DEFAULT_MSG ${VISAGE_ARGS_LIST})
set(VISAGE_INCLUDE_DIRS "${VISAGE_XML_INCLUDE_DIR}" "${VISAGE_OPENCV_INCLUDE_DIR}" "${VISAGE_OPENCV2_INCLUDE_DIR}" "${VISAGE_BASE_INCLUDE_DIR}")
set(VISAGE_LIBRARIES "${VISAGE_CORE_LIBRARY}" "${VISAGE_VISION_LIBRARY}" "${VISAGE_OPENCV_LIBRARY}")
set(VISAGE_LIBRARIES "${VISAGE_CORE_LIBRARY}" "${VISAGE_VISION_LIBRARY}")
if (APPLE)
list(APPEND VISAGE_LIBRARIES ${QuartzCore} ${AppKit} ${QTKit})
list(APPEND VISAGE_LIBRARIES "${CoreVideo}" "${QTKit}" "${IOKit}")
endif ()
mark_as_advanced(VISAGE_INCLUDE_DIRS VISAGE_LIBRARIES)

View file

@ -50,6 +50,6 @@ endif ()
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}")
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
link_shared_dependencies()

View file

@ -76,6 +76,33 @@
}
]
},
{
"name": "scripts",
"label": "Scripts",
"settings": [
{
"name": "persistent_scripts",
"type": "table",
"label": "Persistent Scripts",
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
"columns": [
{
"name": "url",
"label": "Script URL"
},
{
"name": "num_instances",
"label": "# instances",
"default": 1
},
{
"name": "pool",
"label": "Pool"
}
]
}
]
},
{
"name": "audio_env",
"label": "Audio Environment",

View file

@ -1,131 +1,3 @@
// Add your JavaScript for assignment below this line
// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life)
var NUMBER_OF_CELLS_EACH_DIMENSION = 64;
var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION;
var currentCells = [];
var nextCells = [];
var METER_LENGTH = 1;
var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
// randomly populate the cell start values
for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
// create the array to hold this row
currentCells[i] = [];
// create the array to hold this row in the nextCells array
nextCells[i] = [];
for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
currentCells[i][j] = Math.floor(Math.random() * 2);
// put the same value in the nextCells array for first board draw
nextCells[i][j] = currentCells[i][j];
}
}
function isNeighbourAlive(i, j) {
if (i < 0 || i >= NUMBER_OF_CELLS_EACH_DIMENSION
|| i < 0 || j >= NUMBER_OF_CELLS_EACH_DIMENSION) {
return 0;
} else {
return currentCells[i][j];
}
}
function updateCells() {
var i = 0;
var j = 0;
for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
// figure out the number of live neighbours for the i-j cell
var liveNeighbours =
isNeighbourAlive(i + 1, j - 1) + isNeighbourAlive(i + 1, j) + isNeighbourAlive(i + 1, j + 1) +
isNeighbourAlive(i, j - 1) + isNeighbourAlive(i, j + 1) +
isNeighbourAlive(i - 1, j - 1) + isNeighbourAlive(i - 1, j) + isNeighbourAlive(i - 1, j + 1);
if (currentCells[i][j]) {
// live cell
if (liveNeighbours < 2) {
// rule #1 - under-population - this cell will die
// mark it zero to mark the change
nextCells[i][j] = 0;
} else if (liveNeighbours < 4) {
// rule #2 - this cell lives
// mark it -1 to mark no change
nextCells[i][j] = -1;
} else {
// rule #3 - overcrowding - this cell dies
// mark it zero to mark the change
nextCells[i][j] = 0;
}
} else {
// dead cell
if (liveNeighbours == 3) {
// rule #4 - reproduction - this cell revives
// mark it one to mark the change
nextCells[i][j] = 1;
} else {
// this cell stays dead
// mark it -1 for no change
nextCells[i][j] = -1;
}
}
if (Math.random() < 0.001) {
// Random mutation to keep things interesting in there.
nextCells[i][j] = 1;
}
}
}
for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
if (nextCells[i][j] != -1) {
// there has been a change to this cell, change the value in the currentCells array
currentCells[i][j] = nextCells[i][j];
}
}
}
}
function sendNextCells() {
for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
if (nextCells[i][j] != -1) {
// there has been a change to the state of this cell, send it
// find the x and y position for this voxel, z = 0
var x = j * cellScale;
var y = i * cellScale;
// queue a packet to add a voxel for the new cell
var color = (nextCells[i][j] == 1) ? 255 : 1;
Voxels.setVoxel(x, y, 0, cellScale, color, color, color);
}
}
}
}
var sentFirstBoard = false;
function step(deltaTime) {
if (sentFirstBoard) {
// we've already sent the first full board, perform a step in time
updateCells();
} else {
// this will be our first board send
sentFirstBoard = true;
}
sendNextCells();
}
Script.update.connect(step);
Voxels.setPacketsPerSecond(200);
// Here you can put a script that will be run by an assignment-client (AC)
// For examples, please go to http://public.highfidelity.io/scripts
// The directory named acScripts contains assignment-client specific scripts you can try.

View file

@ -363,7 +363,8 @@ function makeTableInputs(setting) {
_.each(setting.columns, function(col) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' value=''>\
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
value='" + (col.default ? col.default : "") + "'>\
</td>"
})
@ -389,8 +390,9 @@ function badgeSidebarForDifferences(changedElement) {
// badge for any settings we have that are not the same or are not present in initialValues
for (var setting in panelJSON) {
if (!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting))) {
if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
(!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) {
badgeValue += 1
}
}

View file

@ -18,6 +18,7 @@
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QProcess>
#include <QtCore/qsharedmemory.h>
#include <QtCore/QStandardPaths>
#include <QtCore/QTimer>
#include <QtCore/QUrlQuery>
@ -28,6 +29,7 @@
#include <LogUtils.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ShutdownEventListener.h>
#include <UUID.h>
#include "DomainServerNodeData.h"
@ -36,9 +38,10 @@
int const DomainServer::EXIT_CODE_REBOOT = 234923;
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_shutdownEventListener(this),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_httpsManager(NULL),
_allAssignments(),
@ -51,7 +54,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
_settingsManager()
_settingsManager(),
_iceServerSocket(ICE_SERVER_DEFAULT_HOSTNAME, ICE_SERVER_DEFAULT_PORT)
{
LogUtils::init();
@ -66,8 +70,12 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_settingsManager.setupConfigMap(arguments());
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
installNativeEventFilter(&ShutdownEventListener::getInstance());
#else
ShutdownEventListener::getInstance();
#endif
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
@ -225,9 +233,27 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
parseAssignmentConfigs(parsedTypes);
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
// check for scripts the user wants to persist from their domain-server config
populateStaticScriptedAssignmentsFromSettings();
LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort);
// no matter the local port, save it to shared mem so that local assignment clients can ask what it is
QSharedMemory* sharedPortMem = new QSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this);
quint16 localPort = nodeList->getNodeSocket().localPort();
// attempt to create the shared memory segment
if (sharedPortMem->create(sizeof(localPort)) || sharedPortMem->attach()) {
sharedPortMem->lock();
memcpy(sharedPortMem->data(), &localPort, sizeof(localPort));
sharedPortMem->unlock();
qDebug() << "Wrote local listening port" << localPort << "to shared memory at key" << DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY;
} else {
qWarning() << "Failed to create and attach to shared memory to share local port with assignment-client children.";
}
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
@ -321,57 +347,67 @@ bool DomainServer::optionallySetupAssignmentPayment() {
void DomainServer::setupAutomaticNetworking() {
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot setup domain-server automatic networking without an access token.";
qDebug() << "Please add an access token to your config file or via the web interface.";
return;
}
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
// setup our timer to check our IP via stun every X seconds
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
_automaticNetworkingSetting =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
// setup a timer to heartbeat with the ice-server every so often
QTimer* iceHeartbeatTimer = new QTimer(this);
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
// call our sendHeartbeatToIceServer immediately anytime a local or public socket changes
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
// attempt to update our public socket now, this will send a heartbeat once we get public socket
requestCurrentPublicSocketViaSTUN();
// in case the STUN lookup is still happening we should re-request a public socket once we get that address
connect(&nodeList->getSTUNSockAddr(), &HifiSockAddr::lookupCompleted,
this, &DomainServer::requestCurrentPublicSocketViaSTUN);
}
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot send heartbeat to data server without an access token.";
qDebug() << "Add an access token to your config file or via the web interface.";
return;
}
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->getSessionUUID();
if (!domainID.isNull()) {
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
// setup our timer to check our IP via stun every X seconds
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
// send public socket changes to the data server so nodes can find us at our new IP
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate);
// attempt to update our sockets now
requestCurrentPublicSocketViaSTUN();
} else {
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
// setup a timer to heartbeat with the ice-server every so often
QTimer* iceHeartbeatTimer = new QTimer(this);
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
// call our sendHeartbeaToIceServer immediately anytime a local or public socket changes
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
// send our heartbeat to data server so it knows what our network settings are
sendHeartbeatToDataServer();
}
// attempt to update our sockets now
requestCurrentPublicSocketViaSTUN();
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
@ -418,8 +454,6 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
if (assignmentType != Assignment::AgentType) {
createStaticAssignmentsForType(assignmentType, assignmentList);
} else {
createScriptedAssignmentsFromList(assignmentList);
}
excludedTypes.insert(assignmentType);
@ -435,35 +469,37 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment
_allAssignments.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
}
void DomainServer::createScriptedAssignmentsFromList(const QVariantList &configList) {
foreach(const QVariant& configVariant, configList) {
if (configVariant.canConvert(QMetaType::QVariantMap)) {
QVariantMap configMap = configVariant.toMap();
// make sure we were passed a URL, otherwise this is an invalid scripted assignment
const QString ASSIGNMENT_URL_KEY = "url";
QString assignmentURL = configMap[ASSIGNMENT_URL_KEY].toString();
if (!assignmentURL.isEmpty()) {
// check the json for a pool
const QString ASSIGNMENT_POOL_KEY = "pool";
QString assignmentPool = configMap[ASSIGNMENT_POOL_KEY].toString();
// check for a number of instances, if not passed then default is 1
const QString ASSIGNMENT_INSTANCES_KEY = "instances";
int numInstances = configMap[ASSIGNMENT_INSTANCES_KEY].toInt();
numInstances = (numInstances == 0 ? 1 : numInstances);
qDebug() << "Adding a static scripted assignment from" << assignmentURL;
for (int i = 0; i < numInstances; i++) {
void DomainServer::populateStaticScriptedAssignmentsFromSettings() {
const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts";
const QVariant* persistentScriptsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), PERSISTENT_SCRIPTS_KEY_PATH);
if (persistentScriptsVariant) {
QVariantList persistentScriptsList = persistentScriptsVariant->toList();
foreach(const QVariant& persistentScriptVariant, persistentScriptsList) {
QVariantMap persistentScript = persistentScriptVariant.toMap();
const QString PERSISTENT_SCRIPT_URL_KEY = "url";
const QString PERSISTENT_SCRIPT_NUM_INSTANCES_KEY = "num_instances";
const QString PERSISTENT_SCRIPT_POOL_KEY = "pool";
if (persistentScript.contains(PERSISTENT_SCRIPT_URL_KEY)) {
// check how many instances of this script to add
int numInstances = persistentScript[PERSISTENT_SCRIPT_NUM_INSTANCES_KEY].toInt();
QString scriptURL = persistentScript[PERSISTENT_SCRIPT_URL_KEY].toString();
QString scriptPool = persistentScript.value(PERSISTENT_SCRIPT_POOL_KEY).toString();
qDebug() << "Adding" << numInstances << "of persistent script at URL" << scriptURL << "- pool" << scriptPool;
for (int i = 0; i < numInstances; ++i) {
// add a scripted assignment to the queue for this instance
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
Assignment::AgentType,
assignmentPool);
scriptAssignment->setPayload(assignmentURL.toUtf8());
// scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies
scriptPool);
scriptAssignment->setPayload(scriptURL.toUtf8());
// add it to static hash so we know we have to keep giving it back out
addStaticAssignmentToAssignmentHash(scriptAssignment);
}
}
@ -821,49 +857,48 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
if (nodeData->isAuthenticated()) {
// if this authenticated node has any interest types, send back those nodes as well
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
nodeList->eachNode([&](const SharedNodePointer& otherNode){
// reset our nodeByteArray and nodeDataStream
QByteArray nodeByteArray;
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
// don't send avatar nodes to other avatars, that will come from avatar mixer
nodeDataStream << *otherNode.data();
// pack the secret that these two nodes will use to communicate with each other
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
->getSessionSecretHash().insert(node->getUUID(), secretUUID);
}
nodeDataStream << secretUUID;
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
// we need to break here and start a new packet
// so send the current one
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
// reset the broadcastPacket structure
broadcastPacket.resize(numBroadcastPacketLeadBytes);
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
}
// append the nodeByteArray to the current state of broadcastDataStream
broadcastPacket.append(nodeByteArray);
}
}
});
}
// always write the last broadcastPacket
@ -960,14 +995,14 @@ void DomainServer::readAvailableDatagrams() {
void DomainServer::setupPendingAssignmentCredits() {
// enumerate the NodeList to find the assigned nodes
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
// check if we have a non-finalized transaction for this node to add this amount to
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
WalletTransaction* existingTransaction = NULL;
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
if (!i.value()->isFinalized()) {
existingTransaction = i.value();
@ -976,16 +1011,16 @@ void DomainServer::setupPendingAssignmentCredits() {
++i;
}
}
qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed();
nodeData->getPaymentIntervalTimer().restart();
const float CREDITS_PER_HOUR = 0.10f;
const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000);
const int SATOSHIS_PER_MSEC = CREDITS_PER_MSEC * SATOSHIS_PER_CREDIT;
float pendingCredits = elapsedMsecsSinceLastPayment * SATOSHIS_PER_MSEC;
if (existingTransaction) {
existingTransaction->incrementAmount(pendingCredits);
} else {
@ -994,7 +1029,7 @@ void DomainServer::setupPendingAssignmentCredits() {
_pendingAssignmentCredits.insert(nodeData->getWalletUUID(), freshTransaction);
}
}
}
});
}
void DomainServer::sendPendingTransactionsToServer() {
@ -1119,11 +1154,12 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;
foreach(const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){
if (node->getLinkedData() && !static_cast<DomainServerNodeData*>(node->getLinkedData())->getUsername().isEmpty()) {
++numConnectedAuthedUsers;
}
}
});
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
@ -1148,8 +1184,7 @@ void DomainServer::performICEUpdates() {
}
void DomainServer::sendHeartbeatToIceServer() {
static HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT);
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR);
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(_iceServerSocket);
}
void DomainServer::sendICEPingPackets() {
@ -1242,8 +1277,9 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
parseNodeDataFromByteArray(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress,
senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID,
nodePublicAddress, nodeLocalAddress);
SharedNodePointer checkInNode = nodeList->nodeWithUUID(nodeUUID);
checkInNode->setPublicSocket(nodePublicAddress);
checkInNode->setLocalSocket(nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow();
@ -1435,15 +1471,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonObject assignedNodesJSON;
// enumerate the NodeList to find the assigned nodes
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
NodeList::getInstance()->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull()) {
// add the node using the UUID as the key
QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
assignedNodesJSON[uuidString] = jsonObjectForNode(node);
}
}
});
assignmentJSON["fulfilled"] = assignedNodesJSON;
@ -1497,12 +1533,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonArray nodesJSONArray;
// enumerate the NodeList to find the assigned nodes
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
LimitedNodeList::getInstance()->eachNode([this, &nodesJSONArray](const SharedNodePointer& node){
// add the node using the UUID as the key
nodesJSONArray.append(jsonObjectForNode(node));
}
});
rootJSON["nodes"] = nodesJSONArray;
@ -2030,16 +2064,9 @@ void DomainServer::addStaticAssignmentsToQueue() {
QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin();
while (staticAssignment != staticHashCopy.end()) {
// add any of the un-matched static assignments to the queue
bool foundMatchingAssignment = false;
// enumerate the nodes and check if there is one with an attached assignment with matching UUID
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
if (node->getUUID() == staticAssignment->data()->getUUID()) {
foundMatchingAssignment = true;
}
}
if (!foundMatchingAssignment) {
if (!NodeList::getInstance()->nodeWithUUID(staticAssignment->data()->getUUID())) {
// this assignment has not been fulfilled - reset the UUID and add it to the assignment queue
refreshStaticAssignmentAndAddToQueue(*staticAssignment);
}

View file

@ -27,7 +27,6 @@
#include "DomainServerSettingsManager.h"
#include "DomainServerWebSessionData.h"
#include "ShutdownEventListener.h"
#include "WalletTransaction.h"
#include "PendingAssignedNodeData.h"
@ -101,9 +100,9 @@ private:
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
void createScriptedAssignmentsFromList(const QVariantList& configList);
void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList);
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
void populateStaticScriptedAssignmentsFromSettings();
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
@ -125,8 +124,6 @@ private:
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
ShutdownEventListener _shutdownEventListener;
HTTPManager _httpManager;
HTTPSManager* _httpsManager;
@ -154,6 +151,8 @@ private:
QString _automaticNetworkingSetting;
DomainServerSettingsManager _settingsManager;
HifiSockAddr _iceServerSocket;
};

View file

@ -117,6 +117,7 @@ function setupToolBars() {
leftMargin: TEXT_MARGIN,
topMargin: TEXT_MARGIN,
alpha: ALPHA_OFF,
backgroundAlpha: ALPHA_OFF,
visible: true
}));
}

View file

@ -15,11 +15,11 @@ Script.include("libraries/toolBars.js");
var recordingFile = "recording.rec";
function setPlayerOptions() {
MyAvatar.setPlayFromCurrentLocation(true);
MyAvatar.setPlayerUseDisplayName(false);
MyAvatar.setPlayerUseAttachments(false);
MyAvatar.setPlayerUseHeadModel(false);
MyAvatar.setPlayerUseSkeletonModel(false);
MyAvatar.setPlayFromCurrentLocation(true);
MyAvatar.setPlayerUseDisplayName(false);
MyAvatar.setPlayerUseAttachments(false);
MyAvatar.setPlayerUseHeadModel(false);
MyAvatar.setPlayerUseSkeletonModel(false);
}
var windowDimensions = Controller.getViewportDimensions();
@ -47,115 +47,118 @@ setupTimer();
var watchStop = false;
function setupToolBar() {
if (toolBar != null) {
print("Multiple calls to Recorder.js:setupToolBar()");
return;
}
if (toolBar != null) {
print("Multiple calls to Recorder.js:setupToolBar()");
return;
}
Tool.IMAGE_HEIGHT /= 2;
Tool.IMAGE_WIDTH /= 2;
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF);
recordIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-record.svg",
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
x: 0, y: 0,
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: MyAvatar.isPlaying() ? ALPHA_OFF : ALPHA_ON,
visible: true
}, true, !MyAvatar.isRecording());
imageURL: TOOL_ICON_URL + "recording-record.svg",
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
x: 0, y: 0,
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: MyAvatar.isPlaying() ? ALPHA_OFF : ALPHA_ON,
visible: true
}, true, !MyAvatar.isRecording());
var playLoopWidthFactor = 1.65;
playIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play-pause.svg",
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
imageURL: TOOL_ICON_URL + "play-pause.svg",
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
playLoopIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
timerOffset = toolBar.width;
spacing = toolBar.addSpacing(0);
saveIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-save.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.isPlaying() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
imageURL: TOOL_ICON_URL + "recording-save.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.isPlaying() || MyAvatar.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
loadIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-upload.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.isPlaying()) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
imageURL: TOOL_ICON_URL + "recording-upload.svg",
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: (MyAvatar.isRecording() || MyAvatar.isPlaying()) ? ALPHA_OFF : ALPHA_ON,
visible: true
}, false);
}
function setupTimer() {
timer = Overlays.addOverlay("text", {
font: { size: 15 },
text: (0.00).toFixed(3),
backgroundColor: COLOR_OFF,
x: 0, y: 0,
width: 0,
height: 0,
alpha: 1.0,
visible: true
});
timer = Overlays.addOverlay("text", {
font: { size: 15 },
text: (0.00).toFixed(3),
backgroundColor: COLOR_OFF,
x: 0, y: 0,
width: 0,
height: 0,
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true
});
slider = { x: 0, y: 0,
w: 200, h: 20,
pos: 0.0, // 0.0 <= pos <= 1.0
w: 200, h: 20,
pos: 0.0, // 0.0 <= pos <= 1.0
};
slider.background = Overlays.addOverlay("text", {
text: "",
backgroundColor: { red: 128, green: 128, blue: 128 },
x: slider.x, y: slider.y,
width: slider.w,
height: slider.h,
alpha: 1.0,
visible: true
});
text: "",
backgroundColor: { red: 128, green: 128, blue: 128 },
x: slider.x, y: slider.y,
width: slider.w,
height: slider.h,
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true
});
slider.foreground = Overlays.addOverlay("text", {
text: "",
backgroundColor: { red: 200, green: 200, blue: 200 },
x: slider.x, y: slider.y,
width: slider.pos * slider.w,
height: slider.h,
alpha: 1.0,
visible: true
});
text: "",
backgroundColor: { red: 200, green: 200, blue: 200 },
x: slider.x, y: slider.y,
width: slider.pos * slider.w,
height: slider.h,
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true
});
}
function updateTimer() {
var text = "";
if (MyAvatar.isRecording()) {
text = formatTime(MyAvatar.recorderElapsed());
var text = "";
if (MyAvatar.isRecording()) {
text = formatTime(MyAvatar.recorderElapsed());
} else {
text = formatTime(MyAvatar.playerElapsed()) + " / " +
formatTime(MyAvatar.playerLength());
}
} else {
text = formatTime(MyAvatar.playerElapsed()) + " / " +
formatTime(MyAvatar.playerLength());
}
Overlays.editOverlay(timer, {
text: text
})
Overlays.editOverlay(timer, {
text: text
})
toolBar.changeSpacing(text.length * 8 + ((MyAvatar.isRecording()) ? 15 : 0), spacing);
if (MyAvatar.isRecording()) {
@ -165,57 +168,56 @@ function updateTimer() {
}
Overlays.editOverlay(slider.foreground, {
width: slider.pos * slider.w
});
width: slider.pos * slider.w
});
}
function formatTime(time) {
var MIN_PER_HOUR = 60;
var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000;
var MIN_PER_HOUR = 60;
var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000;
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR);
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN);
var seconds = Math.floor(time / MSEC_PER_SEC);
seconds = time / MSEC_PER_SEC;
var seconds = Math.floor(time / MSEC_PER_SEC);
seconds = time / MSEC_PER_SEC;
var text = "";
text += (hours > 0) ? hours + ":" :
"";
text += (minutes > 0) ? ((minutes < 10 && text != "") ? "0" : "") + minutes + ":" :
"";
text += ((seconds < 10 && text != "") ? "0" : "") + seconds.toFixed(3);
return text;
var text = "";
text += (hours > 0) ? hours + ":" :
"";
text += (minutes > 0) ? ((minutes < 10 && text != "") ? "0" : "") + minutes + ":" :
"";
text += ((seconds < 10 && text != "") ? "0" : "") + seconds.toFixed(3);
return text;
}
function moveUI() {
var relative = { x: 70, y: 40 };
toolBar.move(relative.x,
windowDimensions.y - relative.y);
Overlays.editOverlay(timer, {
x: relative.x + timerOffset - ToolBar.SPACING,
y: windowDimensions.y - relative.y - ToolBar.SPACING
});
var relative = { x: 70, y: 40 };
toolBar.move(relative.x, windowDimensions.y - relative.y);
Overlays.editOverlay(timer, {
x: relative.x + timerOffset - ToolBar.SPACING,
y: windowDimensions.y - relative.y - ToolBar.SPACING
});
slider.x = relative.x - ToolBar.SPACING;
slider.y = windowDimensions.y - relative.y - slider.h - ToolBar.SPACING;
Overlays.editOverlay(slider.background, {
x: slider.x,
y: slider.y,
});
x: slider.x,
y: slider.y,
});
Overlays.editOverlay(slider.foreground, {
x: slider.x,
y: slider.y,
});
x: slider.x,
y: slider.y,
});
}
function mousePressEvent(event) {
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (recordIcon === toolBar.clicked(clickedOverlay, false) && !MyAvatar.isPlaying()) {
if (!MyAvatar.isRecording()) {
@ -267,7 +269,7 @@ function mousePressEvent(event) {
if (!MyAvatar.isRecording() && !MyAvatar.isPlaying() && MyAvatar.playerLength() != 0) {
recordingFile = Window.save("Save recording to file", ".", "Recordings (*.hfr)");
if (!(recordingFile === "null" || recordingFile === null || recordingFile === "")) {
MyAvatar.saveRecording(recordingFile);
MyAvatar.saveRecording(recordingFile);
}
}
} else if (loadIcon === toolBar.clicked(clickedOverlay)) {
@ -283,8 +285,8 @@ function mousePressEvent(event) {
}
}
} else if (MyAvatar.playerLength() > 0 &&
slider.x < event.x && event.x < slider.x + slider.w &&
slider.y < event.y && event.y < slider.y + slider.h) {
slider.x < event.x && event.x < slider.x + slider.w &&
slider.y < event.y && event.y < slider.y + slider.h) {
isSliding = true;
slider.pos = (event.x - slider.x) / slider.w;
MyAvatar.setPlayerTime(slider.pos * MyAvatar.playerLength());
@ -308,14 +310,13 @@ function mouseReleaseEvent(event) {
}
function update() {
var newDimensions = Controller.getViewportDimensions();
if (windowDimensions.x != newDimensions.x ||
windowDimensions.y != newDimensions.y) {
windowDimensions = newDimensions;
moveUI();
}
var newDimensions = Controller.getViewportDimensions();
if (windowDimensions.x != newDimensions.x || windowDimensions.y != newDimensions.y) {
windowDimensions = newDimensions;
moveUI();
}
updateTimer();
updateTimer();
if (watchStop && !MyAvatar.isPlaying()) {
watchStop = false;
@ -326,12 +327,12 @@ function update() {
}
function scriptEnding() {
if (MyAvatar.isRecording()) {
MyAvatar.stopRecording();
}
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
}
if (MyAvatar.isRecording()) {
MyAvatar.stopRecording();
}
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
}
toolBar.cleanup();
Overlays.deleteOverlay(timer);
Overlays.deleteOverlay(slider.background);

View file

@ -1,844 +0,0 @@
//
// audioReflectorTools.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/14/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// Tools for manipulating the attributes of the AudioReflector behavior
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("libraries/globals.js");
var delayScale = 100.0;
var fanoutScale = 10.0;
var speedScale = 20;
var factorScale = 5.0;
var localFactorScale = 1.0;
var reflectiveScale = 100.0;
var diffusionScale = 100.0;
var absorptionScale = 100.0;
var combFilterScale = 50.0;
var originalScale = 2.0;
var echoesScale = 2.0;
// these three properties are bound together, if you change one, the others will also change
var reflectiveRatio = AudioReflector.getReflectiveRatio();
var diffusionRatio = AudioReflector.getDiffusionRatio();
var absorptionRatio = AudioReflector.getAbsorptionRatio();
var reflectiveThumbX;
var diffusionThumbX;
var absorptionThumbX;
function setReflectiveRatio(reflective) {
var total = diffusionRatio + absorptionRatio + (reflective / reflectiveScale);
diffusionRatio = diffusionRatio / total;
absorptionRatio = absorptionRatio / total;
reflectiveRatio = (reflective / reflectiveScale) / total;
updateRatioValues();
}
function setDiffusionRatio(diffusion) {
var total = (diffusion / diffusionScale) + absorptionRatio + reflectiveRatio;
diffusionRatio = (diffusion / diffusionScale) / total;
absorptionRatio = absorptionRatio / total;
reflectiveRatio = reflectiveRatio / total;
updateRatioValues();
}
function setAbsorptionRatio(absorption) {
var total = diffusionRatio + (absorption / absorptionScale) + reflectiveRatio;
diffusionRatio = diffusionRatio / total;
absorptionRatio = (absorption / absorptionScale) / total;
reflectiveRatio = reflectiveRatio / total;
updateRatioValues();
}
function updateRatioSliders() {
reflectiveThumbX = reflectiveMinThumbX + ((reflectiveMaxThumbX - reflectiveMinThumbX) * reflectiveRatio);
diffusionThumbX = diffusionMinThumbX + ((diffusionMaxThumbX - diffusionMinThumbX) * diffusionRatio);
absorptionThumbX = absorptionMinThumbX + ((absorptionMaxThumbX - absorptionMinThumbX) * absorptionRatio);
Overlays.editOverlay(reflectiveThumb, { x: reflectiveThumbX } );
Overlays.editOverlay(diffusionThumb, { x: diffusionThumbX } );
Overlays.editOverlay(absorptionThumb, { x: absorptionThumbX } );
}
function updateRatioValues() {
AudioReflector.setReflectiveRatio(reflectiveRatio);
AudioReflector.setDiffusionRatio(diffusionRatio);
AudioReflector.setAbsorptionRatio(absorptionRatio);
}
var topY = 250;
var sliderHeight = 35;
var delayY = topY;
topY += sliderHeight;
var delayLabel = Overlays.addOverlay("text", {
x: 40,
y: delayY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 12,
leftMargin: 5,
text: "Delay:"
});
var delaySlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: delayY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var delayMinThumbX = 110;
var delayMaxThumbX = delayMinThumbX + 110;
var delayThumbX = delayMinThumbX + ((delayMaxThumbX - delayMinThumbX) * (AudioReflector.getPreDelay() / delayScale));
var delayThumb = Overlays.addOverlay("image", {
x: delayThumbX,
y: delayY + 9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 255, green: 0, blue: 0},
alpha: 1
});
var fanoutY = topY;
topY += sliderHeight;
var fanoutLabel = Overlays.addOverlay("text", {
x: 40,
y: fanoutY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 12,
leftMargin: 5,
text: "Fanout:"
});
var fanoutSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: fanoutY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var fanoutMinThumbX = 110;
var fanoutMaxThumbX = fanoutMinThumbX + 110;
var fanoutThumbX = fanoutMinThumbX + ((fanoutMaxThumbX - fanoutMinThumbX) * (AudioReflector.getDiffusionFanout() / fanoutScale));
var fanoutThumb = Overlays.addOverlay("image", {
x: fanoutThumbX,
y: fanoutY + 9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 255, green: 255, blue: 0},
alpha: 1
});
var speedY = topY;
topY += sliderHeight;
var speedLabel = Overlays.addOverlay("text", {
x: 40,
y: speedY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Speed\nin ms/m:"
});
var speedSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: speedY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var speedMinThumbX = 110;
var speedMaxThumbX = speedMinThumbX + 110;
var speedThumbX = speedMinThumbX + ((speedMaxThumbX - speedMinThumbX) * (AudioReflector.getSoundMsPerMeter() / speedScale));
var speedThumb = Overlays.addOverlay("image", {
x: speedThumbX,
y: speedY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 0, green: 255, blue: 0},
alpha: 1
});
var factorY = topY;
topY += sliderHeight;
var factorLabel = Overlays.addOverlay("text", {
x: 40,
y: factorY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Attenuation\nFactor:"
});
var factorSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: factorY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var factorMinThumbX = 110;
var factorMaxThumbX = factorMinThumbX + 110;
var factorThumbX = factorMinThumbX + ((factorMaxThumbX - factorMinThumbX) * (AudioReflector.getDistanceAttenuationScalingFactor() / factorScale));
var factorThumb = Overlays.addOverlay("image", {
x: factorThumbX,
y: factorY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 0, green: 0, blue: 255},
alpha: 1
});
var localFactorY = topY;
topY += sliderHeight;
var localFactorLabel = Overlays.addOverlay("text", {
x: 40,
y: localFactorY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Local\nFactor:"
});
var localFactorSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: localFactorY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var localFactorMinThumbX = 110;
var localFactorMaxThumbX = localFactorMinThumbX + 110;
var localFactorThumbX = localFactorMinThumbX + ((localFactorMaxThumbX - localFactorMinThumbX) * (AudioReflector.getLocalAudioAttenuationFactor() / localFactorScale));
var localFactorThumb = Overlays.addOverlay("image", {
x: localFactorThumbX,
y: localFactorY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 0, green: 128, blue: 128},
alpha: 1
});
var combFilterY = topY;
topY += sliderHeight;
var combFilterLabel = Overlays.addOverlay("text", {
x: 40,
y: combFilterY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Comb Filter\nWindow:"
});
var combFilterSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: combFilterY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var combFilterMinThumbX = 110;
var combFilterMaxThumbX = combFilterMinThumbX + 110;
var combFilterThumbX = combFilterMinThumbX + ((combFilterMaxThumbX - combFilterMinThumbX) * (AudioReflector.getCombFilterWindow() / combFilterScale));
var combFilterThumb = Overlays.addOverlay("image", {
x: combFilterThumbX,
y: combFilterY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 128, green: 128, blue: 0},
alpha: 1
});
var reflectiveY = topY;
topY += sliderHeight;
var reflectiveLabel = Overlays.addOverlay("text", {
x: 40,
y: reflectiveY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Reflective\nRatio:"
});
var reflectiveSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: reflectiveY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var reflectiveMinThumbX = 110;
var reflectiveMaxThumbX = reflectiveMinThumbX + 110;
reflectiveThumbX = reflectiveMinThumbX + ((reflectiveMaxThumbX - reflectiveMinThumbX) * AudioReflector.getReflectiveRatio());
var reflectiveThumb = Overlays.addOverlay("image", {
x: reflectiveThumbX,
y: reflectiveY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var diffusionY = topY;
topY += sliderHeight;
var diffusionLabel = Overlays.addOverlay("text", {
x: 40,
y: diffusionY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Diffusion\nRatio:"
});
var diffusionSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: diffusionY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var diffusionMinThumbX = 110;
var diffusionMaxThumbX = diffusionMinThumbX + 110;
diffusionThumbX = diffusionMinThumbX + ((diffusionMaxThumbX - diffusionMinThumbX) * AudioReflector.getDiffusionRatio());
var diffusionThumb = Overlays.addOverlay("image", {
x: diffusionThumbX,
y: diffusionY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 0, green: 255, blue: 255},
alpha: 1
});
var absorptionY = topY;
topY += sliderHeight;
var absorptionLabel = Overlays.addOverlay("text", {
x: 40,
y: absorptionY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Absorption\nRatio:"
});
var absorptionSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: absorptionY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var absorptionMinThumbX = 110;
var absorptionMaxThumbX = absorptionMinThumbX + 110;
absorptionThumbX = absorptionMinThumbX + ((absorptionMaxThumbX - absorptionMinThumbX) * AudioReflector.getAbsorptionRatio());
var absorptionThumb = Overlays.addOverlay("image", {
x: absorptionThumbX,
y: absorptionY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 255, green: 0, blue: 255},
alpha: 1
});
var originalY = topY;
topY += sliderHeight;
var originalLabel = Overlays.addOverlay("text", {
x: 40,
y: originalY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Original\nMix:"
});
var originalSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: originalY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var originalMinThumbX = 110;
var originalMaxThumbX = originalMinThumbX + 110;
var originalThumbX = originalMinThumbX + ((originalMaxThumbX - originalMinThumbX) * (AudioReflector.getOriginalSourceAttenuation() / originalScale));
var originalThumb = Overlays.addOverlay("image", {
x: originalThumbX,
y: originalY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 128, green: 128, blue: 0},
alpha: 1
});
var echoesY = topY;
topY += sliderHeight;
var echoesLabel = Overlays.addOverlay("text", {
x: 40,
y: echoesY,
width: 60,
height: sliderHeight,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 255, blue: 255},
topMargin: 6,
leftMargin: 5,
text: "Echoes\nMix:"
});
var echoesSlider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: echoesY, width: 150, height: sliderHeight},
subImage: { x: 46, y: 0, width: 200, height: 71 },
imageURL: HIFI_PUBLIC_BUCKET + "images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
var echoesMinThumbX = 110;
var echoesMaxThumbX = echoesMinThumbX + 110;
var echoesThumbX = echoesMinThumbX + ((echoesMaxThumbX - echoesMinThumbX) * (AudioReflector.getEchoesAttenuation() / echoesScale));
var echoesThumb = Overlays.addOverlay("image", {
x: echoesThumbX,
y: echoesY+9,
width: 18,
height: 17,
imageURL: HIFI_PUBLIC_BUCKET + "images/thumb.png",
color: { red: 128, green: 128, blue: 0},
alpha: 1
});
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
Overlays.deleteOverlay(factorLabel);
Overlays.deleteOverlay(factorThumb);
Overlays.deleteOverlay(factorSlider);
Overlays.deleteOverlay(combFilterLabel);
Overlays.deleteOverlay(combFilterThumb);
Overlays.deleteOverlay(combFilterSlider);
Overlays.deleteOverlay(localFactorLabel);
Overlays.deleteOverlay(localFactorThumb);
Overlays.deleteOverlay(localFactorSlider);
Overlays.deleteOverlay(speedLabel);
Overlays.deleteOverlay(speedThumb);
Overlays.deleteOverlay(speedSlider);
Overlays.deleteOverlay(delayLabel);
Overlays.deleteOverlay(delayThumb);
Overlays.deleteOverlay(delaySlider);
Overlays.deleteOverlay(fanoutLabel);
Overlays.deleteOverlay(fanoutThumb);
Overlays.deleteOverlay(fanoutSlider);
Overlays.deleteOverlay(reflectiveLabel);
Overlays.deleteOverlay(reflectiveThumb);
Overlays.deleteOverlay(reflectiveSlider);
Overlays.deleteOverlay(diffusionLabel);
Overlays.deleteOverlay(diffusionThumb);
Overlays.deleteOverlay(diffusionSlider);
Overlays.deleteOverlay(absorptionLabel);
Overlays.deleteOverlay(absorptionThumb);
Overlays.deleteOverlay(absorptionSlider);
Overlays.deleteOverlay(echoesLabel);
Overlays.deleteOverlay(echoesThumb);
Overlays.deleteOverlay(echoesSlider);
Overlays.deleteOverlay(originalLabel);
Overlays.deleteOverlay(originalThumb);
Overlays.deleteOverlay(originalSlider);
}
Script.scriptEnding.connect(scriptEnding);
var count = 0;
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
function update(deltaTime) {
count++;
}
Script.update.connect(update);
// The slider is handled in the mouse event callbacks.
var movingSliderDelay = false;
var movingSliderFanout = false;
var movingSliderSpeed = false;
var movingSliderFactor = false;
var movingSliderCombFilter = false;
var movingSliderLocalFactor = false;
var movingSliderReflective = false;
var movingSliderDiffusion = false;
var movingSliderAbsorption = false;
var movingSliderOriginal = false;
var movingSliderEchoes = false;
var thumbClickOffsetX = 0;
function mouseMoveEvent(event) {
if (movingSliderDelay) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < delayMinThumbX) {
newThumbX = delayMinThumbX;
}
if (newThumbX > delayMaxThumbX) {
newThumbX = delayMaxThumbX;
}
Overlays.editOverlay(delayThumb, { x: newThumbX } );
var delay = ((newThumbX - delayMinThumbX) / (delayMaxThumbX - delayMinThumbX)) * delayScale;
AudioReflector.setPreDelay(delay);
}
if (movingSliderFanout) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < fanoutMinThumbX) {
newThumbX = fanoutMinThumbX;
}
if (newThumbX > fanoutMaxThumbX) {
newThumbX = fanoutMaxThumbX;
}
Overlays.editOverlay(fanoutThumb, { x: newThumbX } );
var fanout = Math.round(((newThumbX - fanoutMinThumbX) / (fanoutMaxThumbX - fanoutMinThumbX)) * fanoutScale);
AudioReflector.setDiffusionFanout(fanout);
}
if (movingSliderSpeed) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < speedMinThumbX) {
newThumbX = speedMminThumbX;
}
if (newThumbX > speedMaxThumbX) {
newThumbX = speedMaxThumbX;
}
Overlays.editOverlay(speedThumb, { x: newThumbX } );
var speed = ((newThumbX - speedMinThumbX) / (speedMaxThumbX - speedMinThumbX)) * speedScale;
AudioReflector.setSoundMsPerMeter(speed);
}
if (movingSliderFactor) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < factorMinThumbX) {
newThumbX = factorMminThumbX;
}
if (newThumbX > factorMaxThumbX) {
newThumbX = factorMaxThumbX;
}
Overlays.editOverlay(factorThumb, { x: newThumbX } );
var factor = ((newThumbX - factorMinThumbX) / (factorMaxThumbX - factorMinThumbX)) * factorScale;
AudioReflector.setDistanceAttenuationScalingFactor(factor);
}
if (movingSliderCombFilter) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < combFilterMinThumbX) {
newThumbX = combFilterMminThumbX;
}
if (newThumbX > combFilterMaxThumbX) {
newThumbX = combFilterMaxThumbX;
}
Overlays.editOverlay(combFilterThumb, { x: newThumbX } );
var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale;
AudioReflector.setCombFilterWindow(combFilter);
}
if (movingSliderLocalFactor) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < localFactorMinThumbX) {
newThumbX = localFactorMminThumbX;
}
if (newThumbX > localFactorMaxThumbX) {
newThumbX = localFactorMaxThumbX;
}
Overlays.editOverlay(localFactorThumb, { x: newThumbX } );
var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale;
AudioReflector.setLocalAudioAttenuationFactor(localFactor);
}
if (movingSliderAbsorption) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < absorptionMinThumbX) {
newThumbX = absorptionMminThumbX;
}
if (newThumbX > absorptionMaxThumbX) {
newThumbX = absorptionMaxThumbX;
}
Overlays.editOverlay(absorptionThumb, { x: newThumbX } );
var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale;
setAbsorptionRatio(absorption);
}
if (movingSliderReflective) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < reflectiveMinThumbX) {
newThumbX = reflectiveMminThumbX;
}
if (newThumbX > reflectiveMaxThumbX) {
newThumbX = reflectiveMaxThumbX;
}
Overlays.editOverlay(reflectiveThumb, { x: newThumbX } );
var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale;
setReflectiveRatio(reflective);
}
if (movingSliderDiffusion) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < diffusionMinThumbX) {
newThumbX = diffusionMminThumbX;
}
if (newThumbX > diffusionMaxThumbX) {
newThumbX = diffusionMaxThumbX;
}
Overlays.editOverlay(diffusionThumb, { x: newThumbX } );
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
setDiffusionRatio(diffusion);
}
if (movingSliderEchoes) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < echoesMinThumbX) {
newThumbX = echoesMminThumbX;
}
if (newThumbX > echoesMaxThumbX) {
newThumbX = echoesMaxThumbX;
}
Overlays.editOverlay(echoesThumb, { x: newThumbX } );
var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale;
AudioReflector.setEchoesAttenuation(echoes);
}
if (movingSliderOriginal) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < originalMinThumbX) {
newThumbX = originalMminThumbX;
}
if (newThumbX > originalMaxThumbX) {
newThumbX = originalMaxThumbX;
}
Overlays.editOverlay(originalThumb, { x: newThumbX } );
var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale;
AudioReflector.setOriginalSourceAttenuation(original);
}
}
// we also handle click detection in our mousePressEvent()
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == delayThumb) {
movingSliderDelay = true;
thumbClickOffsetX = event.x - delayThumbX;
}
if (clickedOverlay == fanoutThumb) {
movingSliderFanout = true;
thumbClickOffsetX = event.x - fanoutThumbX;
}
if (clickedOverlay == speedThumb) {
movingSliderSpeed = true;
thumbClickOffsetX = event.x - speedThumbX;
}
if (clickedOverlay == factorThumb) {
movingSliderFactor = true;
thumbClickOffsetX = event.x - factorThumbX;
}
if (clickedOverlay == localFactorThumb) {
movingSliderLocalFactor = true;
thumbClickOffsetX = event.x - localFactorThumbX;
}
if (clickedOverlay == combFilterThumb) {
movingSliderCombFilter = true;
thumbClickOffsetX = event.x - combFilterThumbX;
}
if (clickedOverlay == diffusionThumb) {
movingSliderDiffusion = true;
thumbClickOffsetX = event.x - diffusionThumbX;
}
if (clickedOverlay == absorptionThumb) {
movingSliderAbsorption = true;
thumbClickOffsetX = event.x - absorptionThumbX;
}
if (clickedOverlay == reflectiveThumb) {
movingSliderReflective = true;
thumbClickOffsetX = event.x - reflectiveThumbX;
}
if (clickedOverlay == originalThumb) {
movingSliderOriginal = true;
thumbClickOffsetX = event.x - originalThumbX;
}
if (clickedOverlay == echoesThumb) {
movingSliderEchoes = true;
thumbClickOffsetX = event.x - echoesThumbX;
}
}
function mouseReleaseEvent(event) {
if (movingSliderDelay) {
movingSliderDelay = false;
var delay = ((newThumbX - delayMinThumbX) / (delayMaxThumbX - delayMinThumbX)) * delayScale;
AudioReflector.setPreDelay(delay);
delayThumbX = newThumbX;
}
if (movingSliderFanout) {
movingSliderFanout = false;
var fanout = Math.round(((newThumbX - fanoutMinThumbX) / (fanoutMaxThumbX - fanoutMinThumbX)) * fanoutScale);
AudioReflector.setDiffusionFanout(fanout);
fanoutThumbX = newThumbX;
}
if (movingSliderSpeed) {
movingSliderSpeed = false;
var speed = ((newThumbX - speedMinThumbX) / (speedMaxThumbX - speedMinThumbX)) * speedScale;
AudioReflector.setSoundMsPerMeter(speed);
speedThumbX = newThumbX;
}
if (movingSliderFactor) {
movingSliderFactor = false;
var factor = ((newThumbX - factorMinThumbX) / (factorMaxThumbX - factorMinThumbX)) * factorScale;
AudioReflector.setDistanceAttenuationScalingFactor(factor);
factorThumbX = newThumbX;
}
if (movingSliderCombFilter) {
movingSliderCombFilter = false;
var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale;
AudioReflector.setCombFilterWindow(combFilter);
combFilterThumbX = newThumbX;
}
if (movingSliderLocalFactor) {
movingSliderLocalFactor = false;
var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale;
AudioReflector.setLocalAudioAttenuationFactor(localFactor);
localFactorThumbX = newThumbX;
}
if (movingSliderReflective) {
movingSliderReflective = false;
var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale;
setReflectiveRatio(reflective);
reflectiveThumbX = newThumbX;
updateRatioSliders();
}
if (movingSliderDiffusion) {
movingSliderDiffusion = false;
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
setDiffusionRatio(diffusion);
diffusionThumbX = newThumbX;
updateRatioSliders();
}
if (movingSliderAbsorption) {
movingSliderAbsorption = false;
var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale;
setAbsorptionRatio(absorption);
absorptionThumbX = newThumbX;
updateRatioSliders();
}
if (movingSliderEchoes) {
movingSliderEchoes = false;
var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale;
AudioReflector.setEchoesAttenuation(echoes);
echoesThumbX = newThumbX;
}
if (movingSliderOriginal) {
movingSliderOriginal = false;
var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale;
AudioReflector.setOriginalSourceAttenuation(original);
originalThumbX = newThumbX;
}
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);

View file

@ -13,18 +13,13 @@
//
var numButterflies = 20;
var numButterflies = 25;
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// Multiply vector by scalar
function vScalarMult(v, s) {
var rval = { x: v.x * s, y: v.y * s, z: v.z * s };
return rval;
}
// Create a random vector with individual lengths between a,b
function randVector(a, b) {
@ -32,50 +27,36 @@ function randVector(a, b) {
return rval;
}
// Returns a vector which is fraction of the way between a and b
function vInterpolate(a, b, fraction) {
var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction };
return rval;
}
var startTimeInSeconds = new Date().getTime() / 1000;
var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.76, y: 0.825, z: 0.20 };
var lifeTime = 600; // lifetime of the butterflies in seconds
var range = 3.0; // Over what distance in meters do you want the flock to fly around
var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.0, y: 0.4, z: 0.2 };
var lifeTime = 3600; // One hour lifespan
var range = 7.0; // Over what distance in meters do you want the flock to fly around
var frame = 0;
var CHANCE_OF_MOVING = 0.9;
var BUTTERFLY_GRAVITY = 0;
var BUTTERFLY_FLAP_SPEED = 0.5;
var BUTTERFLY_VELOCITY = 0.55;
var DISTANCE_IN_FRONT_OF_ME = 1.5;
var DISTANCE_ABOVE_ME = 1.5;
var flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(
var FIXED_LOCATION = false;
if (!FIXED_LOCATION) {
var flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME),
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME)));
} else {
var flockPosition = { x: 4999.6, y: 4986.5, z: 5003.5 };
}
// set these pitch, yaw, roll to the needed values to orient the model as you want it
var pitchInDegrees = 270.0;
var yawInDegrees = 0.0;
var rollInDegrees = 0.0;
var pitchInRadians = pitchInDegrees / 180.0 * Math.PI;
var yawInRadians = yawInDegrees / 180.0 * Math.PI;
var rollInRadians = rollInDegrees / 180.0 * Math.PI;
var rotation = Quat.fromPitchYawRollDegrees(pitchInDegrees, yawInDegrees, rollInDegrees);//experimental
// This is our butterfly object
function defineButterfly(entityID, targetPosition) {
this.entityID = entityID;
this.previousFlapOffset = 0;
this.targetPosition = targetPosition;
this.moving = false;
}
// Array of butterflies
var butterflies = [];
function addButterfly() {
// Decide the size of butterfly
var color = { red: 100, green: 100, blue: 100 };
@ -88,26 +69,24 @@ function addButterfly() {
size = MINSIZE + Math.random() * RANGESIZE;
var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize));
flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME),
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME)));
var GRAVITY = -0.2;
var newFrameRate = 20 + Math.random() * 30;
var properties = {
type: "Model",
lifetime: lifeTime,
position: Vec3.sum(randVector(-range, range), flockPosition),
velocity: { x: 0, y: 0.0, z: 0 },
gravity: { x: 0, y: 1.0, z: 0 },
damping: 0.1,
rotation: Quat.fromPitchYawRollDegrees(-80 + Math.random() * 20, Math.random() * 360.0, 0.0),
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: GRAVITY, z: 0 },
damping: 0.9999,
dimensions: dimensions,
color: color,
rotation: rotation,
animationURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/content/butterfly/butterfly.fbx",
animationIsPlaying: true,
animationSettings: "{\"firstFrame\":0,\"fps\":" + newFrameRate + ",\"frameIndex\":0,\"hold\":false,\"lastFrame\":10000,\"loop\":true,\"running\":true,\"startAutomatically\":false}",
modelURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/content/butterfly/butterfly.fbx"
};
butterflies.push(new defineButterfly(Entities.addEntity(properties), properties.position));
butterflies.push(Entities.addEntity(properties));
}
// Generate the butterflies
@ -116,117 +95,37 @@ for (var i = 0; i < numButterflies; i++) {
}
// Main update function
function updateButterflies(deltaTime) {
// Check to see if we've been running long enough that our butterflies are dead
var nowTimeInSeconds = new Date().getTime() / 1000;
if ((nowTimeInSeconds - startTimeInSeconds) >= lifeTime) {
Script.stop();
return;
}
function updateButterflies(deltaTime) {
frame++;
// Only update every third frame because we don't need to do it too quickly
if ((frame % 3) == 0) {
flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME),
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME)));
// Update all the butterflies
var CHANCE_OF_IMPULSE = 0.04;
for (var i = 0; i < numButterflies; i++) {
entityID = Entities.identifyEntity(butterflies[i].entityID);
butterflies[i].entityID = entityID;
var properties = Entities.getEntityProperties(entityID);
if (properties.position.y > flockPosition.y + getRandomFloat(0.0,0.3)){ //0.3 //ceiling
properties.gravity.y = - 3.0;
properties.damping.y = 1.0;
properties.velocity.y = 0;
properties.velocity.x = properties.velocity.x;
properties.velocity.z = properties.velocity.z;
if (properties.velocity.x < 0.5){
butterflies[i].moving = false;
if (Math.random() < CHANCE_OF_IMPULSE) {
if (!butterflies[i].isKnownID) {
butterflies[i] = Entities.identifyEntity(butterflies[i]);
}
if (properties.velocity.z < 0.5){
butterflies[i].moving = false;
}
}
if (properties.velocity.y <= -0.2) {
properties.velocity.y = 0.22;
properties.velocity.x = properties.velocity.x;
properties.velocity.z = properties.velocity.z;
}
if (properties.position.y < flockPosition.y - getRandomFloat(0.0,0.3)) { //-0.3 // floor
properties.velocity.y = 0.9;
properties.gravity.y = - 4.0;
properties.velocity.x = properties.velocity.x;
properties.velocity.z = properties.velocity.z;
if (properties.velocity.x < 0.5){
butterflies[i].moving = false;
}
if (properties.velocity.z < 0.5){
butterflies[i].moving = false;
}
}
// Begin movement by getting a target
if (butterflies[i].moving == false) {
if (Math.random() < CHANCE_OF_MOVING) {
var targetPosition = Vec3.sum(randVector(-range, range), flockPosition);
if (targetPosition.x < 0) {
targetPosition.x = 0;
}
if (targetPosition.y < 0) {
targetPosition.y = 0;
}
if (targetPosition.z < 0) {
targetPosition.z = 0;
}
if (targetPosition.x > TREE_SCALE) {
targetPosition.x = TREE_SCALE;
}
if (targetPosition.y > TREE_SCALE) {
targetPosition.y = TREE_SCALE;
}
if (targetPosition.z > TREE_SCALE) {
targetPosition.z = TREE_SCALE;
}
butterflies[i].targetPosition = targetPosition;
butterflies[i].moving = true;
var properties = Entities.getEntityProperties(butterflies[i]);
if (Vec3.length(Vec3.subtract(properties.position, flockPosition)) > range) {
Entities.editEntity(butterflies[i], { position: flockPosition } );
} else if (properties.velocity.y < 0.0) {
// If falling, Create a new direction and impulse
var HORIZ_SCALE = 0.50;
var VERT_SCALE = 0.50;
var newHeading = Math.random() * 360.0;
var newVelocity = Vec3.multiply(HORIZ_SCALE, Quat.getFront(Quat.fromPitchYawRollDegrees(0.0, newHeading, 0.0)));
newVelocity.y = (Math.random() + 0.5) * VERT_SCALE;
Entities.editEntity(butterflies[i], { rotation: Quat.fromPitchYawRollDegrees(-80 + Math.random() * 20, newHeading, (Math.random() - 0.5) * 10),
velocity: newVelocity } );
}
}
// If we are moving, move towards the target
if (butterflies[i].moving) {
var holding = properties.velocity.y;
var desiredVelocity = Vec3.subtract(butterflies[i].targetPosition, properties.position);
desiredVelocity = vScalarMult(Vec3.normalize(desiredVelocity), BUTTERFLY_VELOCITY);
properties.velocity = vInterpolate(properties.velocity, desiredVelocity, 0.5);
properties.velocity.y = holding ;
// If we are near the target, we should get a new target
var halfLargestDimension = Vec3.length(properties.dimensions) / 2.0;
if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (halfLargestDimension)) {
butterflies[i].moving = false;
}
var yawRads = Math.atan2(properties.velocity.z, properties.velocity.x);
yawRads = yawRads + Math.PI / 2.0;
var newOrientation = Quat.fromPitchYawRollRadians(pitchInRadians, yawRads, rollInRadians);
properties.rotation = newOrientation;
}
// Use a cosine wave offset to make it look like its flapping.
var offset = Math.cos(nowTimeInSeconds * BUTTERFLY_FLAP_SPEED) * (halfLargestDimension);
properties.position.y = properties.position.y + (offset - butterflies[i].previousFlapOffset);
// Change position relative to previous offset.
butterflies[i].previousFlapOffset = offset;
Entities.editEntity(entityID, properties);
}
// Check to see if we've been running long enough that our butterflies are dead
var nowTimeInSeconds = new Date().getTime() / 1000;
if ((nowTimeInSeconds - startTimeInSeconds) >= lifeTime) {
Script.stop();
return;
}
}
}
@ -237,6 +136,6 @@ Script.update.connect(updateButterflies);
// Delete our little friends if script is stopped
Script.scriptEnding.connect(function() {
for (var i = 0; i < numButterflies; i++) {
Entities.deleteEntity(butterflies[i].entityID);
Entities.deleteEntity(butterflies[i]);
}
});

View file

@ -22,12 +22,7 @@ var velocity = {
y: 0,
z: 1 };
var gravity = {
x: 0,
y: 0,
z: 0 };
var damping = 0.1;
var damping = 0;
var color = {
red: 255,
@ -42,22 +37,17 @@ function draw(deltaTime) {
var colorGreen = { red: 0, green: 255, blue: 0 };
var startPosition = {
x: 2,
y: 0,
y: 1,
z: 2 };
var largeRadius = 0.5;
var verySlow = {
x: 0.01,
y: 0,
z: 0.01 };
var properties = {
type: "Sphere",
collisionsWillMove: true,
position: startPosition,
dimensions: {x: largeRadius, y: largeRadius, z: largeRadius},
registrationPoint: { x: 0.5, y: 0.5, z: 0.5 },
color: colorGreen,
velocity: verySlow,
gravity: gravity,
damping: damping,
lifetime: 20
};
@ -71,7 +61,7 @@ function draw(deltaTime) {
var center = {
x: 0,
y: 0,
y: 1,
z: 0 };
var entitySize = 0.1;
@ -97,9 +87,9 @@ function draw(deltaTime) {
collisionsWillMove: true,
position: center,
dimensions: {x: entitySize, y: entitySize, z: entitySize},
registrationPoint: { x: 0.5, y: 0.5, z: 0.5 },
color: color,
velocity: velocity,
gravity: gravity,
damping: damping,
lifetime: 20
};

View file

@ -9,9 +9,9 @@
//
Script.load("lookWithTouch.js");
Script.load("editVoxels.js");
Script.load("editModels.js");
Script.load("editEntities.js");
Script.load("selectAudioDevice.js");
Script.load("hydraMove.js");
Script.load("headMove.js");
Script.load("inspect.js");
Script.load("lobby.js");

View file

@ -0,0 +1,36 @@
//
// developerMenuItems.js
// examples
//
// Created by Brad Hefta-Gaub on 2/24/14
// Copyright 2013 High Fidelity, Inc.
//
// Adds a bunch of developer and debugging menu items
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function setupMenus() {
if (!Menu.menuExists("Developer")) {
Menu.addMenu("Developer");
}
if (!Menu.menuExists("Developer > Entities")) {
Menu.addMenu("Developer > Entities");
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Bounds", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Triangles", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Bounds", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Children", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Attempt Render Entities as Scene", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false });
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false });
}
}
function scriptEnding() {
Menu.removeMenu("Developer > Entities");
}
setupMenus();
Script.scriptEnding.connect(scriptEnding);

View file

@ -51,8 +51,8 @@ var toolWidth = 50;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = false;
var allowSmallModels = false;
var allowLargeModels = true;
var allowSmallModels = true;
var wantEntityGlow = false;
var SPAWN_DISTANCE = 1;
@ -140,29 +140,27 @@ var toolBar = (function () {
menuItemHeight = Tool.IMAGE_HEIGHT / 2 - 2;
loadURLMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset,
height: menuItemHeight,
backgroundColor: menuBackgroundColor,
topMargin: menuItemMargin,
text: "Model URL",
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
loadFileMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset + menuItemHeight,
height: menuItemHeight,
backgroundColor: menuBackgroundColor,
topMargin: menuItemMargin,
text: "Model File",
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
menuItemWidth = Math.max(Overlays.textWidth(loadURLMenuItem, "Model URL"),
Overlays.textWidth(loadFileMenuItem, "Model File")) + 20;
menuItemWidth = Math.max(Overlays.textSize(loadURLMenuItem, "Model URL").width,
Overlays.textSize(loadFileMenuItem, "Model File").width) + 20;
Overlays.editOverlay(loadURLMenuItem, { width: menuItemWidth });
Overlays.editOverlay(loadFileMenuItem, { width: menuItemWidth });
@ -215,6 +213,28 @@ var toolBar = (function () {
Overlays.editOverlay(loadFileMenuItem, { visible: active });
}
that.setActive = function(active) {
if (active != isActive) {
isActive = active;
if (!isActive) {
entityListTool.setVisible(false);
gridTool.setVisible(false);
grid.setEnabled(false);
propertiesTool.setVisible(false);
selectionManager.clearSelections();
cameraManager.disable();
} else {
cameraManager.enable();
entityListTool.setVisible(true);
gridTool.setVisible(true);
propertiesTool.setVisible(true);
grid.setEnabled(true);
}
}
toolBar.selectTool(activeButton, active);
};
var RESIZE_INTERVAL = 50;
var RESIZE_TIMEOUT = 20000;
var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL;
@ -290,21 +310,7 @@ var toolBar = (function () {
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (activeButton === toolBar.clicked(clickedOverlay)) {
isActive = !isActive;
if (!isActive) {
entityListTool.setVisible(false);
gridTool.setVisible(false);
grid.setEnabled(false);
propertiesTool.setVisible(false);
selectionManager.clearSelections();
cameraManager.disable();
} else {
cameraManager.enable();
entityListTool.setVisible(true);
gridTool.setVisible(true);
grid.setEnabled(true);
propertiesTool.setVisible(true);
}
that.setActive(!isActive);
return true;
}
@ -465,7 +471,7 @@ function rayPlaneIntersection(pickRay, point, normal) {
function findClickedEntity(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var foundIntersection = Entities.findRayIntersection(pickRay);
var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking
if (!foundIntersection.accurate) {
return null;
@ -476,7 +482,6 @@ function findClickedEntity(event) {
var identify = Entities.identifyEntity(foundEntity);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")");
selectionManager.clearSelections();
return null;
}
foundEntity = identify;
@ -485,74 +490,18 @@ function findClickedEntity(event) {
return { pickRay: pickRay, entityID: foundEntity };
}
var mouseHasMovedSincePress = false;
function mousePressEvent(event) {
mouseHasMovedSincePress = false;
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
return;
}
if (isActive) {
var entitySelected = false;
if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
// Event handled; do nothing.
return;
} else {
var result = findClickedEntity(event);
if (result === null) {
selectionManager.clearSelections();
return;
}
var pickRay = result.pickRay;
var foundEntity = result.entityID;
var properties = Entities.getEntityProperties(foundEntity);
if (isLocked(properties)) {
print("Model locked " + properties.id);
} else {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
entitySelected = true;
selectedEntityID = foundEntity;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
if (!event.isShifted) {
selectionManager.clearSelections();
}
selectionManager.addEntity(foundEntity);
print("Model selected: " + foundEntity.id);
}
}
}
if (entitySelected) {
selectionDisplay.select(selectedEntityID, event);
}
} else if (Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)) {
var result = findClickedEntity(event);
@ -572,6 +521,7 @@ function mousePressEvent(event) {
var highlightedEntityID = { isKnownID: false };
function mouseMoveEvent(event) {
mouseHasMovedSincePress = true;
if (isActive) {
// allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing
if (selectionDisplay.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) {
@ -615,6 +565,76 @@ function mouseReleaseEvent(event) {
}
cameraManager.mouseReleaseEvent(event);
if (!mouseHasMovedSincePress) {
mouseClickEvent(event);
}
}
function mouseClickEvent(event) {
if (!isActive) {
return;
}
var result = findClickedEntity(event);
if (result === null) {
if (!event.isShifted) {
selectionManager.clearSelections();
}
return;
}
var pickRay = result.pickRay;
var foundEntity = result.entityID;
var properties = Entities.getEntityProperties(foundEntity);
if (isLocked(properties)) {
print("Model locked " + properties.id);
} else {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
entitySelected = true;
selectedEntityID = foundEntity;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
if (!event.isShifted) {
selectionManager.clearSelections();
}
var toggle = event.isShifted;
selectionManager.addEntity(foundEntity, toggle);
print("Model selected: " + foundEntity.id);
selectionDisplay.select(selectedEntityID, event);
}
}
}
Controller.mousePressEvent.connect(mousePressEvent);
@ -626,6 +646,7 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var modelMenuAddedDelete = false;
var originalLightsArePickable = Entities.getLightsArePickable();
function setupModelMenus() {
print("setupModelMenus()");
// adj our menuitems
@ -643,18 +664,21 @@ function setupModelMenus() {
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Select Large Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
afterItem: "Allow Selecting of Small Models", isCheckable: true });
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 });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED,
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
Entities.setLightsArePickable(false);
}
setupModelMenus(); // do this when first running our script.
@ -669,13 +693,13 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Model List...");
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
Menu.removeSeparator("File", "Models");
Menu.removeMenuItem("File", "Export Models");
Menu.removeMenuItem("File", "Import Models");
Menu.removeMenuItem("Developer", "Debug Ryans Rotation Problems");
Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED);
Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS);
@ -694,6 +718,7 @@ Script.scriptEnding.connect(function() {
if (exportMenu) {
exportMenu.close();
}
Entities.setLightsArePickable(originalLightsArePickable);
});
// Do some stuff regularly, like check for placement of various overlays
@ -704,10 +729,12 @@ Script.update.connect(function (deltaTime) {
});
function handeMenuEvent(menuItem) {
if (menuItem == "Allow Select Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Select Small Models");
} else if (menuItem == "Allow Select Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Select Large Models");
if (menuItem == "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
} else if (menuItem == "Allow Selecting of Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
} else if (menuItem == "Allow Selecting of Lights") {
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
} else if (menuItem == "Delete") {
if (SelectionManager.hasSelection()) {
print(" Delete Entities");
@ -794,6 +821,13 @@ function handeMenuEvent(menuItem) {
Menu.menuItemEvent.connect(handeMenuEvent);
Controller.keyPressEvent.connect(function(event) {
if (event.text == 'w' || event.text == 'a' || event.text == 's' || event.text == 'd'
|| event.text == 'UP' || event.text == 'DOWN' || event.text == 'LEFT' || event.text == 'RIGHT') {
toolBar.setActive(false);
}
});
Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "`") {
@ -805,9 +839,11 @@ Controller.keyReleaseEvent.connect(function (event) {
selectionDisplay.toggleSpaceMode();
} else if (event.text == "f") {
if (isActive) {
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
if (selectionManager.hasSelection()) {
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
}
} else if (event.text == '[') {
if (isActive) {
@ -967,7 +1003,9 @@ PropertiesTool = function(opts) {
type: 'update',
};
if (selectionManager.hasSelection()) {
data.id = selectionManager.selections[0].id;
data.properties = Entities.getEntityProperties(selectionManager.selections[0]);
data.properties.rotation = Quat.safeEulerAngles(data.properties.rotation);
}
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
});
@ -976,8 +1014,59 @@ PropertiesTool = function(opts) {
print(data);
data = JSON.parse(data);
if (data.type == "update") {
selectionManager.saveProperties();
if (data.properties.rotation !== undefined) {
var rotation = data.properties.rotation;
data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z);
}
Entities.editEntity(selectionManager.selections[0], data.properties);
pushCommandForSelections();
selectionManager._update();
} else if (data.type == "action") {
if (data.action == "moveSelectionToGrid") {
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
var dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2),
var diff = { x: 0, y: dY, z: 0 };
for (var i = 0; i < selectionManager.selections.length; i++) {
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
var newPosition = Vec3.sum(properties.position, diff);
Entities.editEntity(selectionManager.selections[i], {
position: newPosition,
});
}
pushCommandForSelections();
selectionManager._update();
}
} else if (data.action == "moveAllToGrid") {
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (var i = 0; i < selectionManager.selections.length; i++) {
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2;
var dY = grid.getOrigin().y - bottomY;
var diff = { x: 0, y: dY, z: 0 };
var newPosition = Vec3.sum(properties.position, diff);
Entities.editEntity(selectionManager.selections[i], {
position: newPosition,
});
}
pushCommandForSelections();
selectionManager._update();
}
} else if (data.action == "resetToNaturalDimensions") {
if (selectionManager.hasSelection()) {
selectionManager.saveProperties();
for (var i = 0; i < selectionManager.selections.length; i++) {
var properties = selectionManager.savedProperties[selectionManager.selections[i].id];
Entities.editEntity(selectionManager.selections[i], {
dimensions: properties.naturalDimensions,
});
}
pushCommandForSelections();
selectionManager._update();
}
}
}
});

View file

@ -206,6 +206,7 @@ var progressDialog = (function () {
height: backgroundHeight,
imageURL: backgroundUrl,
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
@ -216,6 +217,7 @@ var progressDialog = (function () {
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
@ -226,6 +228,7 @@ var progressDialog = (function () {
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
@ -1169,29 +1172,27 @@ var toolBar = (function () {
menuItemHeight = Tool.IMAGE_HEIGHT / 2 - 2;
loadURLMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset,
height: menuItemHeight,
backgroundColor: menuBackgroundColor,
topMargin: menuItemMargin,
text: "Model URL",
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
loadFileMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset + menuItemHeight,
height: menuItemHeight,
backgroundColor: menuBackgroundColor,
topMargin: menuItemMargin,
text: "Model File",
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
menuItemWidth = Math.max(Overlays.textWidth(loadURLMenuItem, "Model URL"),
Overlays.textWidth(loadFileMenuItem, "Model File")) + 20;
menuItemWidth = Math.max(Overlays.textSize(loadURLMenuItem, "Model URL").width,
Overlays.textSize(loadFileMenuItem, "Model File").width) + 20;
Overlays.editOverlay(loadURLMenuItem, { width: menuItemWidth });
Overlays.editOverlay(loadFileMenuItem, { width: menuItemWidth });
@ -1493,6 +1494,7 @@ var ExportMenu = function (opts) {
width: scaleViewWidth,
height: height,
alpha: 0.0,
backgroundAlpha: 0.0,
color: { red: 255, green: 255, blue: 255 },
text: "1"
});
@ -2480,6 +2482,7 @@ function Tooltip() {
text: "",
color: { red: 228, green: 228, blue: 228 },
alpha: 0.8,
backgroundAlpha: 0.8,
visible: false
});
this.show = function (doShow) {
@ -2554,7 +2557,7 @@ function mousePressEvent(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
var foundIntersection = Entities.findRayIntersection(pickRay);
var foundIntersection = Entities.findRayIntersection(pickRay, true); // we want precision picking here
if(!foundIntersection.accurate) {
return;
@ -2807,6 +2810,7 @@ function mouseReleaseEvent(event) {
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var modelMenuAddedDelete = false;
var originalLightsArePickable = Entities.getLightsArePickable();
function setupModelMenus() {
print("setupModelMenus()");
// adj our menuitems
@ -2824,15 +2828,18 @@ function setupModelMenus() {
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L",
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Select Large Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Selecting of Large Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
afterItem: "Allow Selecting of Small Models", isCheckable: true });
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" });
Entities.setLightsArePickable(false);
}
@ -2846,8 +2853,9 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Model List...");
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
Menu.removeSeparator("File", "Models");
Menu.removeMenuItem("File", "Export Models");
@ -2865,6 +2873,7 @@ function scriptEnding() {
if (exportMenu) {
exportMenu.close();
}
Entities.setLightsArePickable(originalLightsArePickable);
}
Script.scriptEnding.connect(scriptEnding);
@ -2890,10 +2899,12 @@ function showPropertiesForm(editModelID) {
function handeMenuEvent(menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Allow Select Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Select Small Models");
} else if (menuItem == "Allow Select Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Select Large Models");
if (menuItem == "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
} else if (menuItem == "Allow Selecting of Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
} else if (menuItem == "Allow Selecting of Lights") {
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
} else if (menuItem == "Delete") {
if (leftController.grabbing) {
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);

View file

@ -319,7 +319,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
topMargin: 13,
text: this.scale.toString(),
alpha: 0.9,
backgroundAlpha: 0.0,
visible: editToolsOn
});
this.powerOverlay = Overlays.addOverlay("text", {
@ -327,7 +327,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
leftMargin: 28,
text: this.power.toString(),
alpha: 0.0,
backgroundAlpha: 0.0,
visible: false
});
this.setScale = function(scale) {

View file

@ -0,0 +1,20 @@
//
// changeColorOnCollision.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 12/8/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
//
(function(){
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
this.collisionWithEntity = function(myID, otherID, collisionInfo) {
Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} });
};
})

View file

@ -117,7 +117,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
topMargin: 13,
text: this.scale.toString(),
alpha: 0.0,
backgroundAlpha: 0.0,
visible: editToolsOn,
color: activeUIColor
});
@ -126,7 +126,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
leftMargin: 28,
text: this.power.toString(),
alpha: 0.0,
backgroundAlpha: 0.0,
visible: false,
color: activeUIColor
});

View file

@ -16,11 +16,15 @@ var AXIS_STRAFE = Joysticks.AXIS_LEFT_X;
var AXIS_FORWARD = Joysticks.AXIS_LEFT_Y;
var AXIS_ROTATE = Joysticks.AXIS_RIGHT_X;
var BUTTON_SPRINT = Joysticks.BUTTON_LEFT_STICK;
var BUTTON_TOGGLE_MIRROR = Joysticks.BUTTON_FACE_LEFT;
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 = null; // Disable for now
var BUTTON_WARP_FORWARD = Joysticks.BUTTON_DPAD_UP;
var BUTTON_WARP_BACKWARD = Joysticks.BUTTON_DPAD_DOWN;
@ -31,7 +35,8 @@ var BUTTON_WARP_RIGHT = Joysticks.BUTTON_DPAD_RIGHT;
var WARP_DISTANCE = 1;
// Walk speed in m/s
var MOVE_SPEED = 2;
var MOVE_SPEED = 0.5;
var MOVE_SPRINT_SPEED = 2;
// Amount to rotate in radians
var ROTATE_INCREMENT = Math.PI / 8;
@ -46,9 +51,14 @@ var WARP_PICK_MAX_DISTANCE = 100;
var flyDownButtonState = false;
var flyUpButtonState = false;
// When toggling to mirror mode, this stores the mode the user was previously in
// so it can be toggled back to.
var toggledFromCameraMode = 'first person';
// 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 sprintButtonState = false;
var warpActive = false;
var warpPosition = { x: 0, y: 0, z: 0 };
@ -173,6 +183,18 @@ function reportButtonValue(button, newValue, oldValue) {
MyAvatar.orientation = Quat.multiply(
Quat.fromPitchYawRollRadians(0, Math.PI, 0), MyAvatar.orientation);
}
} else if (button == BUTTON_SPRINT) {
sprintButtonState = newValue;
} else if (button == BUTTON_TOGGLE_MIRROR) {
if (newValue) {
var currentMode = Camera.mode;
if (currentMode != "mirror") {
toggledFromCameraMode = currentMode;
}
Camera.mode = "mirror";
} else {
Camera.mode = toggledFromCameraMode;
}
} else if (newValue) {
var direction = null;
@ -209,9 +231,10 @@ function update(dt) {
var move = copyVec3(moveDirection);
move.y = 0;
if (Vec3.length(move) > 0) {
speed = sprintButtonState ? MOVE_SPRINT_SPEED : MOVE_SPEED;
velocity = Vec3.multiplyQbyV(Camera.getOrientation(), move);
velocity.y = 0;
velocity = Vec3.multiply(Vec3.normalize(velocity), MOVE_SPEED);
velocity = Vec3.multiply(Vec3.normalize(velocity), speed);
}
if (moveDirection.y != 0) {

View file

@ -116,7 +116,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
topMargin: 13,
text: this.scale.toString(),
alpha: 0.0,
backgroundAlpha: 0.0,
visible: editToolsOn,
color: activeUIColor
});
@ -125,7 +125,7 @@ function ScaleSelector() {
width: this.SECOND_PART, height: this.height,
leftMargin: 28,
text: this.power.toString(),
alpha: 0.0,
backgroundAlpha: 0.0,
visible: false,
color: activeUIColor
});

View file

@ -96,6 +96,7 @@ function printVector(string, vector) {
function shootBullet(position, velocity) {
var BULLET_SIZE = 0.01;
var BULLET_LIFETIME = 20.0;
var BULLET_GRAVITY = -0.02;
Entities.addEntity(
{ type: "Sphere",
@ -103,6 +104,7 @@ function shootBullet(position, velocity) {
dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE },
color: { red: 10, green: 10, blue: 10 },
velocity: velocity,
lifetime: BULLET_LIFETIME,
gravity: { x: 0, y: BULLET_GRAVITY, z: 0 },
damping: 0 });
@ -118,6 +120,7 @@ function shootBullet(position, velocity) {
function shootTarget() {
var TARGET_SIZE = 0.25;
var TARGET_GRAVITY = -0.6;
var TARGET_LIFETIME = 300.0;
var TARGET_UP_VELOCITY = 3.0;
var TARGET_FWD_VELOCITY = 5.0;
var DISTANCE_TO_LAUNCH_FROM = 3.0;
@ -140,7 +143,7 @@ function shootTarget() {
color: { red: 0, green: 200, blue: 200 },
velocity: velocity,
gravity: { x: 0, y: TARGET_GRAVITY, z: 0 },
lifetime: 1000.0,
lifetime: TARGET_LIFETIME,
damping: 0.99 });
// Record start time

View file

@ -11,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var gamepads = {};
var debug = false;
var willMove = false;
@ -61,13 +63,6 @@ function restoreCameraState() {
Camera.mode = oldMode;
}
function activateWarp() {
if (warpActive) return;
warpActive = true;
updateWarp();
}
var WATCH_AVATAR_DISTANCE = 2.5;
var sound = SoundCache.getSound("http://public.highfidelity.io/sounds/Footsteps/FootstepW2Right-12db.wav");
@ -131,6 +126,22 @@ function updateWarp() {
});
}
function activateWarp() {
if (warpActive) return;
warpActive = true;
movingWithHead = true;
hipsToEyes = MyAvatar.getEyePosition().y - MyAvatar.position.y;
headStartPosition = MyAvatar.getTrackedHeadPosition();
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
headStartRoll = MyAvatar.getHeadFinalRoll();
headStartYaw = MyAvatar.getHeadFinalYaw();
deltaYaw = 0.0;
warpPosition = MyAvatar.position;
warpPosition.y += hipsToEyes;
updateWarp();
}
function finishWarp() {
if (!warpActive) return;
warpActive = false;
@ -151,6 +162,9 @@ function finishWarp() {
cameraPosition = Vec3.subtract(MyAvatar.position, Vec3.multiplyQbyV(Camera.getOrientation(), { x: 0, y: -hipsToEyes, z: -hipsToEyes * WATCH_AVATAR_DISTANCE }));
Camera.setPosition(cameraPosition);
playSound();
if (watchAvatar) {
restoreCountdownTimer = RESTORE_TIME;
}
}
}
@ -173,16 +187,6 @@ function update(deltaTime) {
Controller.keyPressEvent.connect(function(event) {
if (event.text == "SPACE" && !event.isAutoRepeat && !movingWithHead) {
keyDownTime = 0.0;
movingWithHead = true;
hipsToEyes = MyAvatar.getEyePosition().y - MyAvatar.position.y;
headStartPosition = MyAvatar.getTrackedHeadPosition();
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
headStartRoll = MyAvatar.getHeadFinalRoll();
headStartYaw = MyAvatar.getHeadFinalYaw();
deltaYaw = 0.0;
warpPosition = MyAvatar.position;
warpPosition.y += hipsToEyes;
activateWarp();
}
});
@ -208,11 +212,40 @@ Controller.keyReleaseEvent.connect(function(event) {
}
timeSinceLastUp = 0.0;
finishWarp();
if (watchAvatar) {
restoreCountdownTimer = RESTORE_TIME;
}
}
});
function reportButtonValue(button, newValue, oldValue) {
if (button == Joysticks.BUTTON_FACE_RIGHT) {
if (newValue) {
activateWarp();
} else {
finishWarp();
}
}
}
Script.update.connect(update);
function addJoystick(gamepad) {
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);

View file

@ -70,21 +70,29 @@
};
function loaded() {
var elID = document.getElementById("property-id");
var elType = document.getElementById("property-type");
var elLocked = document.getElementById("property-locked");
var elVisible = document.getElementById("property-visible");
var elPositionX = document.getElementById("property-pos-x");
var elPositionY = document.getElementById("property-pos-y");
var elPositionZ = document.getElementById("property-pos-z");
var elMoveSelectionToGrid = document.getElementById("move-selection-to-grid");
var elMoveAllToGrid = document.getElementById("move-all-to-grid");
var elDimensionsX = document.getElementById("property-dim-x");
var elDimensionsY = document.getElementById("property-dim-y");
var elDimensionsZ = document.getElementById("property-dim-z");
var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions");
var elRegistrationX = document.getElementById("property-reg-x");
var elRegistrationY = document.getElementById("property-reg-y");
var elRegistrationZ = document.getElementById("property-reg-z");
var elRotationX = document.getElementById("property-rot-x");
var elRotationY = document.getElementById("property-rot-y");
var elRotationZ = document.getElementById("property-rot-z");
var elLinearVelocityX = document.getElementById("property-lvel-x");
var elLinearVelocityY = document.getElementById("property-lvel-y");
var elLinearVelocityZ = document.getElementById("property-lvel-z");
@ -103,13 +111,14 @@
var elIgnoreForCollisions = document.getElementById("property-ignore-for-collisions");
var elCollisionsWillMove = document.getElementById("property-collisions-will-move");
var elLifetime = document.getElementById("property-lifetime");
var elScriptURL = document.getElementById("property-script-url");
var elBoxSection = document.getElementById("box-section");
var elBoxSections = document.querySelectorAll(".box-section");
var elBoxColorRed = document.getElementById("property-box-red");
var elBoxColorGreen = document.getElementById("property-box-green");
var elBoxColorBlue = document.getElementById("property-box-blue");
var elLightSection = document.getElementById('light-section');
var elLightSections = document.querySelectorAll(".light-section");
var elLightSpotLight = document.getElementById("property-light-spot-light");
var elLightDiffuseRed = document.getElementById("property-light-diffuse-red");
var elLightDiffuseGreen = document.getElementById("property-light-diffuse-green");
@ -129,14 +138,14 @@
var elLightExponent = document.getElementById("property-light-exponent");
var elLightCutoff = document.getElementById("property-light-cutoff");
var elModelSection = document.getElementById("model-section");
var elModelSections = document.querySelectorAll(".model-section");
var elModelURL = document.getElementById("property-model-url");
var elModelAnimationURL = document.getElementById("property-model-animation-url");
var elModelAnimationPlaying = document.getElementById("property-model-animation-playing");
var elModelAnimationFPS = document.getElementById("property-model-animation-fps");
var elModelAnimationFrame = document.getElementById("property-model-animation-frame");
var elTextSection = document.getElementById("text-section");
var elTextSections = document.querySelectorAll(".text-section");
var elTextText = document.getElementById("property-text-text");
var elTextLineHeight = document.getElementById("property-text-line-height");
var elTextTextColorRed = document.getElementById("property-text-text-color-red");
@ -155,107 +164,131 @@
} else {
var properties = data.properties;
elID.innerHTML = data.id;
elType.innerHTML = properties.type;
elLocked.checked = properties.locked;
if (properties.locked) {
disableChildren(document.getElementById("properties"), 'input');
disableChildren(document.getElementById("properties-table"), 'input');
elLocked.removeAttribute('disabled');
} else {
enableChildren(document.getElementById("properties"), 'input');
enableChildren(document.getElementById("properties-table"), 'input');
}
elVisible.checked = properties.visible;
elVisible.checked = properties.visible;
elPositionX.value = properties.position.x.toFixed(2);
elPositionY.value = properties.position.y.toFixed(2);
elPositionZ.value = properties.position.z.toFixed(2);
elPositionX.value = properties.position.x.toFixed(2);
elPositionY.value = properties.position.y.toFixed(2);
elPositionZ.value = properties.position.z.toFixed(2);
elDimensionsX.value = properties.dimensions.x.toFixed(2);
elDimensionsY.value = properties.dimensions.y.toFixed(2);
elDimensionsZ.value = properties.dimensions.z.toFixed(2);
elDimensionsX.value = properties.dimensions.x.toFixed(2);
elDimensionsY.value = properties.dimensions.y.toFixed(2);
elDimensionsZ.value = properties.dimensions.z.toFixed(2);
elRegistrationX.value = properties.registrationPoint.x.toFixed(2);
elRegistrationY.value = properties.registrationPoint.y.toFixed(2);
elRegistrationZ.value = properties.registrationPoint.z.toFixed(2);
elRegistrationX.value = properties.registrationPoint.x.toFixed(2);
elRegistrationY.value = properties.registrationPoint.y.toFixed(2);
elRegistrationZ.value = properties.registrationPoint.z.toFixed(2);
elLinearVelocityX.value = properties.velocity.x.toFixed(2);
elLinearVelocityY.value = properties.velocity.y.toFixed(2);
elLinearVelocityZ.value = properties.velocity.z.toFixed(2);
elLinearDamping.value = properties.damping.toFixed(2);
elRotationX.value = properties.rotation.x.toFixed(2);
elRotationY.value = properties.rotation.y.toFixed(2);
elRotationZ.value = properties.rotation.z.toFixed(2);
elAngularVelocityX.value = properties.angularVelocity.x.toFixed(2);
elAngularVelocityY.value = properties.angularVelocity.y.toFixed(2);
elAngularVelocityZ.value = properties.angularVelocity.z.toFixed(2);
elAngularDamping.value = properties.angularDamping.toFixed(2);
elLinearVelocityX.value = properties.velocity.x.toFixed(2);
elLinearVelocityY.value = properties.velocity.y.toFixed(2);
elLinearVelocityZ.value = properties.velocity.z.toFixed(2);
elLinearDamping.value = properties.damping.toFixed(2);
elGravityX.value = properties.gravity.x.toFixed(2);
elGravityY.value = properties.gravity.y.toFixed(2);
elGravityZ.value = properties.gravity.z.toFixed(2);
elAngularVelocityX.value = properties.angularVelocity.x.toFixed(2);
elAngularVelocityY.value = properties.angularVelocity.y.toFixed(2);
elAngularVelocityZ.value = properties.angularVelocity.z.toFixed(2);
elAngularDamping.value = properties.angularDamping.toFixed(2);
elMass.value = properties.mass.toFixed(2);
elIgnoreForCollisions.checked = properties.ignoreForCollisions;
elCollisionsWillMove.checked = properties.collisionsWillMove;
elLifetime.value = properties.lifetime;
elGravityX.value = properties.gravity.x.toFixed(2);
elGravityY.value = properties.gravity.y.toFixed(2);
elGravityZ.value = properties.gravity.z.toFixed(2);
if (properties.type != "Box") {
elBoxSection.style.display = 'none';
} else {
elBoxSection.style.display = 'block';
elMass.value = properties.mass.toFixed(2);
elIgnoreForCollisions.checked = properties.ignoreForCollisions;
elCollisionsWillMove.checked = properties.collisionsWillMove;
elLifetime.value = properties.lifetime;
elScriptURL.value = properties.script;
elBoxColorRed.value = properties.color.red;
elBoxColorGreen.value = properties.color.green;
elBoxColorBlue.value = properties.color.blue;
if (properties.type != "Box") {
for (var i = 0; i < elBoxSections.length; i++) {
elBoxSections[i].style.display = 'none';
}
} else {
for (var i = 0; i < elBoxSections.length; i++) {
elBoxSections[i].style.display = 'table-row';
}
if (properties.type != "Model") {
elModelSection.style.display = 'none';
} else {
elModelSection.style.display = 'block';
elModelURL.value = properties.modelURL;
elModelAnimationURL.value = properties.animationURL;
elModelAnimationPlaying.checked = properties.animationIsPlaying;
elModelAnimationFPS.value = properties.animationFPS;
elBoxColorRed.value = properties.color.red;
elBoxColorGreen.value = properties.color.green;
elBoxColorBlue.value = properties.color.blue;
}
if (properties.type != "Model") {
for (var i = 0; i < elModelSections.length; i++) {
elModelSections[i].style.display = 'none';
}
} else {
for (var i = 0; i < elModelSections.length; i++) {
elModelSections[i].style.display = 'table-row';
}
if (properties.type != "Text") {
elTextSection.style.display = 'none';
} else {
elTextSection.style.display = 'block';
elModelURL.value = properties.modelURL;
elModelAnimationURL.value = properties.animationURL;
elModelAnimationPlaying.checked = properties.animationIsPlaying;
elModelAnimationFPS.value = properties.animationFPS;
}
elTextText.value = properties.text;
elTextLineHeight.value = properties.lineHeight;
elTextTextColorRed.value = properties.textColor.red;
elTextTextColorGreen.value = properties.textColor.green;
elTextTextColorBlue.value = properties.textColor.blue;
elTextBackgroundColorRed.value = properties.backgroundColor.red;
elTextBackgroundColorGreen.value = properties.backgroundColor.green;
elTextBackgroundColorBlue.value = properties.backgroundColor.blue;
if (properties.type != "Text") {
for (var i = 0; i < elTextSections.length; i++) {
elTextSections[i].style.display = 'none';
}
} else {
for (var i = 0; i < elTextSections.length; i++) {
elTextSections[i].style.display = 'table-row';
}
if (properties.type != "Light") {
elLightSection.style.display = 'none';
} else {
elLightSection.style.display = 'block';
elTextText.value = properties.text;
elTextLineHeight.value = properties.lineHeight;
elTextTextColorRed.value = properties.textColor.red;
elTextTextColorGreen.value = properties.textColor.green;
elTextTextColorBlue.value = properties.textColor.blue;
elTextBackgroundColorRed.value = properties.backgroundColor.red;
elTextBackgroundColorGreen.value = properties.backgroundColor.green;
elTextBackgroundColorBlue.value = properties.backgroundColor.blue;
}
elLightDiffuseRed.value = properties.diffuseColor.red;
elLightDiffuseGreen.value = properties.diffuseColor.green;
elLightDiffuseBlue.value = properties.diffuseColor.blue;
elLightAmbientRed.value = properties.ambientColor.red;
elLightAmbientGreen.value = properties.ambientColor.green;
elLightAmbientBlue.value = properties.ambientColor.blue;
elLightSpecularRed.value = properties.specularColor.red;
elLightSpecularGreen.value = properties.specularColor.green;
elLightSpecularBlue.value = properties.specularColor.blue;
elLightConstantAttenuation.value = properties.constantAttenuation;
elLightLinearAttenuation.value = properties.linearAttenuation;
elLightQuadraticAttenuation.value = properties.quadraticAttenuation;
elLightExponent.value = properties.exponent;
elLightCutoff.value = properties.cutoff;
if (properties.type != "Light") {
for (var i = 0; i < elLightSections.length; i++) {
elLightSections[i].style.display = 'none';
}
} else {
for (var i = 0; i < elLightSections.length; i++) {
elLightSections[i].style.display = 'table-row';
}
elLightDiffuseRed.value = properties.diffuseColor.red;
elLightDiffuseGreen.value = properties.diffuseColor.green;
elLightDiffuseBlue.value = properties.diffuseColor.blue;
elLightAmbientRed.value = properties.ambientColor.red;
elLightAmbientGreen.value = properties.ambientColor.green;
elLightAmbientBlue.value = properties.ambientColor.blue;
elLightSpecularRed.value = properties.specularColor.red;
elLightSpecularGreen.value = properties.specularColor.green;
elLightSpecularBlue.value = properties.specularColor.blue;
elLightConstantAttenuation.value = properties.constantAttenuation;
elLightLinearAttenuation.value = properties.linearAttenuation;
elLightQuadraticAttenuation.value = properties.quadraticAttenuation;
elLightExponent.value = properties.exponent;
elLightCutoff.value = properties.cutoff;
}
}
}
@ -283,6 +316,12 @@
elRegistrationY.addEventListener('change', registrationChangeFunction);
elRegistrationZ.addEventListener('change', registrationChangeFunction);
var rotationChangeFunction = createEmitVec3PropertyUpdateFunction(
'rotation', elRotationX, elRotationY, elRotationZ);
elRotationX.addEventListener('change', rotationChangeFunction);
elRotationY.addEventListener('change', rotationChangeFunction);
elRotationZ.addEventListener('change', rotationChangeFunction);
var velocityChangeFunction = createEmitVec3PropertyUpdateFunction(
'velocity', elLinearVelocityX, elLinearVelocityY, elLinearVelocityZ);
elLinearVelocityX.addEventListener('change', velocityChangeFunction);
@ -307,6 +346,7 @@
elIgnoreForCollisions.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ignoreForCollisions'));
elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove'));
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
@ -345,7 +385,7 @@
elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying'));
elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS'));
elModelAnimationFrame.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFrameIndex'));
elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text'));
elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight'));
@ -361,6 +401,69 @@
elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction);
elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction);
elMoveSelectionToGrid.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveSelectionToGrid",
}));
});
elMoveAllToGrid.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveAllToGrid",
}));
});
elResetToNaturalDimensions.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "resetToNaturalDimensions",
}));
});
var resizing = false;
var startX = 0;
var originalWidth = 0;
var resizeHandleWidth = 10;
var col1 = document.querySelector("#col-label");
document.body.addEventListener('mousemove', function(event) {
if (resizing) {
var dX = event.x - startX;
col1.style.width = (originalWidth + dX) + "px";
}
});
document.body.addEventListener('mouseup', function(event) {
resizing = false;
});
document.body.addEventListener('mouseleave', function(event) {
resizing = false;
});
var els = document.querySelectorAll("#properties-table td");
for (var i = 0; i < els.length; i++) {
var el = els[i];
el.addEventListener('mousemove', function(event) {
if (!resizing) {
var distance = this.offsetWidth - event.offsetX;
if (distance < resizeHandleWidth) {
document.body.style.cursor = "ew-resize";
} else {
document.body.style.cursor = "initial";
}
}
});
el.addEventListener('mousedown', function(event) {
var distance = this.offsetWidth - event.offsetX;
if (distance < resizeHandleWidth) {
startX = event.x;
originalWidth = this.offsetWidth;
resizing = true;
target = this;
}
});
}
}
</script>
</head>
@ -368,272 +471,291 @@
<div class="section-header">
<label>Entity Properties</label>
</div>
<div id="properties" class="grid-section">
<div class="property-section">
<label>Type</label>
<span>
<label id="property-type"></input>
</span>
</div>
<div class="property-section">
<label>Locked</label>
<span>
<table id="properties-table">
<colgroup>
<col id="col-label">
<col>
</colgroup>
<tr>
<td class="label">
ID
</td>
<td>
<label id="property-id" class="selectable"></label>
</td>
</tr>
<tr>
<td class="label">
Type
</td>
<td>
<label id="property-type"></label>
</td>
</tr>
<tr>
<td class="label">Locked</td>
<td>
<input type='checkbox' id="property-locked">
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Visible</label>
<span>
<tr>
<td class="label">Visible</td>
<td>
<input type='checkbox' id="property-visible">
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Position</label>
<span>
X <input class="coord" type='number' id="property-pos-x"></input>
Y <input class="coord" type='number' id="property-pos-y"></input>
Z <input class="coord" type='number' id="property-pos-z"></input>
</span>
</div>
<tr>
<td class="label">Position</td>
<td>
<div class="input-area">X <input class="coord" type='number' id="property-pos-x"></input></div>
<div class="input-area">Y <input class="coord" type='number' id="property-pos-y"></input></div>
<div class="input-area">Z <input class="coord" type='number' id="property-pos-z"></input></div>
<div>
<input type="button" id="move-selection-to-grid" value="Selection to Grid">
<input type="button" id="move-all-to-grid" value="All to Grid">
</div>
</td>
</tr>
<div class="property-section">
<label>Registration</label>
<span>
X <input class="coord" type='number' id="property-reg-x"></input>
Y <input class="coord" type='number' id="property-reg-y"></input>
Z <input class="coord" type='number' id="property-reg-z"></input>
</span>
</div>
<tr>
<td class="label">Registration</td>
<td>
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"></input></div>
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"></input></div>
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"></input></div>
</td>
</tr>
<div class="property-section">
<label>Width</label>
<span>
<input class="coord" type='number' id="property-dim-x"></input>
</span>
</div>
<div class="property-section">
<label>Height</label>
<span>
<input class="coord" type='number' id="property-dim-y"></input>
</span>
</div>
<div class="property-section">
<label>Depth</label>
<span>
<input class="coord" type='number' id="property-dim-z"></input>
</span>
</div>
<tr>
<td class="label">Dimensions</td>
<td>
<div class="input-area">X <input class="coord" type='number' id="property-dim-x"></input></div>
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y"></input></div>
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z"></input></div>
<div>
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
</div>
</td>
</tr>
<div class="property-section">
<label>Linear</label>
<span>
X <input class="coord" type='number' id="property-lvel-x"></input>
Y <input class="coord" type='number' id="property-lvel-y"></input>
Z <input class="coord" type='number' id="property-lvel-z"></input>
</span>
</div>
<div class="property-section">
<label>Linear Damping</label>
<span>
<tr>
<td class="label">Rotation</td>
<td>
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x"></input></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y"></input></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"></input></div>
</td>
</tr>
<tr>
<td class="label">Linear Velocity</td>
<td>
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"></input></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"></input></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"></input></div>
</td>
</tr>
<tr>
<td class="label">Linear Damping</td>
<td>
<input class="coord" type='number' id="property-ldamping"></input>
</span>
</div>
<div class="property-section">
<label>Angular</label>
<span>
Pitch <input class="coord" type='number' id="property-avel-x"></input>
Roll <input class="coord" type='number' id="property-avel-z"></input>
Yaw <input class="coord" type='number' id="property-avel-y"></input>
</span>
</div>
<div class="property-section">
<label>Angular Damping</label>
<span>
</td>
</tr>
<tr>
<td class="label">Angular Velocity</td>
<td>
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></input></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></input></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></input></div>
</td>
</tr>
<tr>
<td class="label">Angular Damping</td>
<td>
<input class="coord" type='number' id="property-adamping"></input>
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Gravity</label>
<span>
X <input class="coord" type='number' id="property-grav-x"></input>
Y <input class="coord" type='number' id="property-grav-y"></input>
Z <input class="coord" type='number' id="property-grav-z"></input>
</span>
</div>
<tr>
<td class="label">Gravity</td>
<td>
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"></input></div>
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"></input></div>
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"></input></div>
</td>
</tr>
<div class="property-section">
<label>Mass</label>
<span>
<tr>
<td class="label">Mass</td>
<td>
<input type='number' id="property-mass"></input>
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Ignore For Collisions</label>
<span>
<tr>
<td class="label">Ignore For Collisions</td>
<td>
<input type='checkbox' id="property-ignore-for-collisions"></input>
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Collisions Will Move</label>
<span>
<tr>
<td class="label">Collisions Will Move</td>
<td>
<input type='checkbox' id="property-collisions-will-move"></input>
</span>
</div>
</td>
</tr>
<div class="property-section">
<label>Lifetime</label>
<span>
<tr>
<td class="label">Lifetime</td>
<td>
<input type='number' id="property-lifetime"></input>
</span>
</div>
</td>
</tr>
<tr>
<td class="label">Script URL</td>
<td>
<input id="property-script-url"></input>
</td>
</tr>
<div id="box-section" class="multi-property-section">
<div class="property-section">
<label>Color</label>
<span>
Red <input class="coord" type='number' id="property-box-red"></input>
Green <input class="coord" type='number' id="property-box-green"></input>
Blue <input class="coord" type='number' id="property-box-blue"></input>
</span>
</div>
</div>
<tr class="box-section">
<td class="label">Color</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-box-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-box-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-box-blue"></input></div>
</td>
</tr>
<div id="model-section" class="multi-property-section">
<div class="property-section">
<label>Model URL</label>
<span>
<input type="text" id="property-model-url"></input>
</span>
</div>
<div class="property-section">
<label>Animation URL</label>
<span>
<input type="text" id="property-model-animation-url"></input>
</span>
</div>
<div class="property-section">
<label>Animation Playing</label>
<span>
<input type='checkbox' id="property-model-animation-playing">
</span>
</div>
<div class="property-section">
<label>Animation FPS</label>
<span>
<input class="coord" type='number' id="property-model-animation-fps"></input>
</span>
</div>
<div class="property-section">
<label>Animation Frame</label>
<span>
<input class="coord" type='number' id="property-model-animation-frame"></input>
</span>
</div>
</div>
<div id="text-section" class="multi-property-section">
<div class="property-section">
<label>Text</label>
<span>
<input type="text" id="property-text-text"></input>
</span>
</div>
<div class="property-section">
<label>Line Height</label>
<span>
<input class="coord" type='number' id="property-text-line-height"></input>
</span>
</div>
<div class="property-section">
<label>Text Color</label>
<span>
Red <input class="coord" type='number' id="property-text-text-color-red"></input>
Green <input class="coord" type='number' id="property-text-text-color-green"></input>
Blue <input class="coord" type='number' id="property-text-text-color-blue"></input>
</span>
</div>
<div class="property-section">
<label>Background Color</label>
<span>
Red <input class="coord" type='number' id="property-text-background-color-red"></input>
Green <input class="coord" type='number' id="property-text-background-color-green"></input>
Blue <input class="coord" type='number' id="property-text-background-color-blue"></input>
</span>
</div>
</div>
<tr class="model-section">
<td class="label">Model URL</td>
<td>
<input type="text" id="property-model-url"></input>
</td>
</tr>
<tr class="model-section">
<td class="label">Animation URL</td>
<td>
<input type="text" id="property-model-animation-url"></input>
</td>
</tr>
<tr class="model-section">
<td class="label">Animation Playing</td>
<td>
<input type='checkbox' id="property-model-animation-playing">
</td>
</tr>
<tr class="model-section">
<td class="label">Animation FPS</td>
<td>
<input class="coord" type='number' id="property-model-animation-fps"></input>
</td>
</tr>
<tr class="model-section">
<td class="label">Animation Frame</td>
<td>
<input class="coord" type='number' id="property-model-animation-frame"></input>
</td>
</tr>
<div id="light-section" class="multi-property-section">
<div class="property-section">
<label>Spot Light</label>
<span>
<input type='checkbox' id="property-light-spot-light">
</span>
</div>
<div class="property-section">
<label>Diffuse</label>
<span>
Red <input class="coord" type='number' id="property-light-diffuse-red"></input>
Green <input class="coord" type='number' id="property-light-diffuse-green"></input>
Blue <input class="coord" type='number' id="property-light-diffuse-blue"></input>
</span>
</div>
<div class="property-section">
<label>Ambient</label>
<span>
Red <input class="coord" type='number' id="property-light-ambient-red"></input>
Green <input class="coord" type='number' id="property-light-ambient-green"></input>
Blue <input class="coord" type='number' id="property-light-ambient-blue"></input>
</span>
</div>
<div class="property-section">
<label>Specular</label>
<span>
Red <input class="coord" type='number' id="property-light-specular-red"></input>
Green <input class="coord" type='number' id="property-light-specular-green"></input>
Blue <input class="coord" type='number' id="property-light-specular-blue"></input>
</span>
</div>
<div class="property-section">
<label>Constant Attenuation</label>
<span>
<input class="coord" type='number' id="property-light-constant-attenuation"></input>
</span>
</div>
<div class="property-section">
<label>Linear Attenuation</label>
<span>
<input class="coord" type='number' id="property-light-linear-attenuation"></input>
</span>
</div>
<div class="property-section">
<label>Quadratic Attenuation</label>
<span>
<input class="coord" type='number' id="property-light-quadratic-attenuation"></input>
</span>
</div>
<div class="property-section">
<label>Exponent</label>
<span>
<input class="coord" type='number' id="property-light-exponent"></input>
</span>
</div>
<div class="property-section">
<label>Cutoff (degrees)</label>
<span>
<input class="coord" type='number' id="property-light-cutoff"></input>
</span>
</div>
</div>
</div>
<tr class="text-section">
<td class="label">Text</td>
<td>
<input type="text" id="property-text-text"></input>
</td>
</tr>
<tr class="text-section">
<td class="label">Line Height</td>
<td>
<input class="coord" type='number' id="property-text-line-height"></input>
</td>
</tr>
<tr class="text-section">
<td class="label">Text Color</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></input></div>
</td>
</tr>
<tr class="text-section">
<td class="label">Background Color</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></input></div>
</td>
</tr>
<tr class="light-section">
<td class="label">Spot Light</td>
<td>
<input type='checkbox' id="property-light-spot-light">
</td>
</tr>
<tr class="light-section">
<td class="label">Diffuse</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-light-diffuse-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-light-diffuse-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-light-diffuse-blue"></input></div>
</td>
</tr>
<tr class="light-section">
<td class="label">Ambient</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-light-ambient-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-light-ambient-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-light-ambient-blue"></input></div>
</td>
</tr>
<tr class="light-section">
<td class="label">Specular</td>
<td>
<div class="input-area">R <input class="coord" type='number' id="property-light-specular-red"></input></div>
<div class="input-area">G <input class="coord" type='number' id="property-light-specular-green"></input></div>
<div class="input-area">B <input class="coord" type='number' id="property-light-specular-blue"></input></div>
</td>
</tr>
<tr class="light-section">
<td class="label">Constant Attenuation</td>
<td>
<input class="coord" type='number' id="property-light-constant-attenuation"></input>
</td>
</tr>
<tr class="light-section">
<td class="label">Linear Attenuation</td>
<td>
<input class="coord" type='number' id="property-light-linear-attenuation"></input>
</td>
</tr>
<tr class="light-section">
<td class="label">Quadratic Attenuation</td>
<td>
<input class="coord" type='number' id="property-light-quadratic-attenuation"></input>
</td>
</tr>
<tr class="light-section">
<td class="label">Exponent</td>
<td>
<input class="coord" type='number' id="property-light-exponent"></input>
</td>
</tr>
<tr class="light-section">
<td class="label">Cutoff (degrees)</td>
<td>
<input class="coord" type='number' id="property-light-cutoff"></input>
</td>
</tr>
</table>
</body>
</html>

View file

@ -6,7 +6,7 @@
var gridColor = { red: 0, green: 0, blue: 0 };
var gridColors = [
{ red: 0, green: 0, blue: 0 },
{ red: 128, green: 128, blue: 128 },
{ red: 255, green: 255, blue: 255 },
{ red: 255, green: 0, blue: 0 },
{ red: 0, green: 255, blue: 0},
{ red: 0, green: 0, blue: 255 },
@ -29,11 +29,11 @@
elPosY.value = origin.y.toFixed(2);
}
if (data.minorGridSpacing) {
if (data.minorGridSpacing !== undefined) {
elMinorSpacing.value = data.minorGridSpacing;
}
if (data.majorGridEvery) {
if (data.majorGridEvery !== undefined) {
elMajorSpacing.value = data.majorGridEvery;
}
@ -41,7 +41,7 @@
gridColor = data.gridColor;
}
if (data.elSnapToGrid !== undefined) {
if (data.snapToGrid !== undefined) {
elSnapToGrid.checked = data.snapToGrid == true;
}

View file

@ -17,13 +17,15 @@ body {
user-select: none;
}
input {
line-height: 2;
}
.selectable {
-webkit-touch-callout: text;
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
.input-left {
display: inline-block;
width: 20px;
cursor: text;
}
.color-box {
@ -63,7 +65,6 @@ input {
.property-section label {
font-weight: bold;
vertical-align: middle;
}
.property-section span {
@ -89,9 +90,10 @@ input[type=button] {
font-size: .9em;
}
input.coord {
width: 6em;
height: 2em;
input {
padding: 2px;
border: 1px solid #999;
background-color: #eee;
}
table#entity-table {
@ -105,7 +107,7 @@ table#entity-table {
cursor: pointer;
}
tr.selected {
#entity-table tr.selected {
background-color: #AAA;
}
@ -130,3 +132,48 @@ th#entity-type {
th#entity-url {
}
div.input-area {
display: inline-block;
}
input {
}
table#properties-table {
border: none;
border-collapse: collapse;
width: 100%;
background-color: #efefef;
font-family: Arial;
font-size: 12px;
table-layout: fixed;
}
#properties-table tr {
border-bottom: 1px solid #e5e5e5;
}
#properties-table td.label {
padding-right: 10px;
border-right: 1px solid #999;
text-align: right;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
height: 1.2em;
}
#properties-table td {
padding: 5px 0px 5px 10px;
}
col#col-label {
width: 130px;
}

View file

@ -245,10 +245,12 @@ function handleGrabBehavior(deltaTime) {
}
// Update for joysticks and move button
var THRUST_DEAD_ZONE = 0.1;
var ROTATE_DEAD_ZONE = 0.1;
function flyWithHydra(deltaTime) {
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
if (thrustJoystickPosition.x != 0 || thrustJoystickPosition.y != 0) {
if (Math.abs(thrustJoystickPosition.x) > THRUST_DEAD_ZONE || Math.abs(thrustJoystickPosition.y) > THRUST_DEAD_ZONE) {
if (thrustMultiplier < MAX_THRUST_MULTIPLIER) {
thrustMultiplier *= 1 + (deltaTime * THRUST_INCREASE_RATE);
}
@ -270,7 +272,7 @@ function flyWithHydra(deltaTime) {
// View Controller
var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER);
if (viewJoystickPosition.x != 0 || viewJoystickPosition.y != 0) {
if (Math.abs(viewJoystickPosition.x) > ROTATE_DEAD_ZONE || Math.abs(viewJoystickPosition.y) > ROTATE_DEAD_ZONE) {
// change the body yaw based on our x controller
var orientation = MyAvatar.orientation;

View file

@ -5,19 +5,89 @@
// Created by Clément Brisset on 7/18/14.
// Copyright 2014 High Fidelity, Inc.
//
// If using Hydra controllers, pulling the triggers makes laser pointers emanate from the respective hands.
// If using a Leap Motion or similar to control your avatar's hands and fingers, pointing with your index fingers makes
// laser pointers emanate from the respective index fingers.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var LEFT = 0;
var RIGHT = 1;
var LEFT_HAND_FLAG = 1;
var RIGHT_HAND_FLAG = 2;
var laserPointer = (function () {
function update() {
var state = ((Controller.getTriggerValue(LEFT) > 0.9) ? LEFT_HAND_FLAG : 0) +
((Controller.getTriggerValue(RIGHT) > 0.9) ? RIGHT_HAND_FLAG : 0);
MyAvatar.setHandState(state);
}
var NUM_FINGERs = 4, // Excluding thumb
fingers = [
[ "LeftHandIndex", "LeftHandMiddle", "LeftHandRing", "LeftHandPinky" ],
[ "RightHandIndex", "RightHandMiddle", "RightHandRing", "RightHandPinky" ]
];
Script.update.connect(update);
function isHandPointing(hand) {
var MINIMUM_TRIGGER_PULL = 0.9;
return Controller.getTriggerValue(hand) > MINIMUM_TRIGGER_PULL;
}
function isFingerPointing(hand) {
// Index finger is pointing if final two bones of middle, ring, and pinky fingers are > 90 degrees w.r.t. index finger
var pointing,
indexDirection,
otherDirection,
f;
pointing = true;
indexDirection = Vec3.subtract(
MyAvatar.getJointPosition(fingers[hand][0] + "4"),
MyAvatar.getJointPosition(fingers[hand][0] + "2")
);
for (f = 1; f < NUM_FINGERs; f += 1) {
otherDirection = Vec3.subtract(
MyAvatar.getJointPosition(fingers[hand][f] + "4"),
MyAvatar.getJointPosition(fingers[hand][f] + "2")
);
pointing = pointing && Vec3.dot(indexDirection, otherDirection) < 0;
}
return pointing;
}
function update() {
var LEFT_HAND = 0,
RIGHT_HAND = 1,
LEFT_HAND_POINTING_FLAG = 1,
RIGHT_HAND_POINTING_FLAG = 2,
FINGER_POINTING_FLAG = 4,
handState;
handState = 0;
if (isHandPointing(LEFT_HAND)) {
handState += LEFT_HAND_POINTING_FLAG;
}
if (isHandPointing(RIGHT_HAND)) {
handState += RIGHT_HAND_POINTING_FLAG;
}
if (handState === 0) {
if (isFingerPointing(LEFT_HAND)) {
handState += LEFT_HAND_POINTING_FLAG;
}
if (isFingerPointing(RIGHT_HAND)) {
handState += RIGHT_HAND_POINTING_FLAG;
}
if (handState !== 0) {
handState += FINGER_POINTING_FLAG;
}
}
MyAvatar.setHandState(handState);
}
return {
update: update
};
}());
Script.update.connect(laserPointer.update);

View file

@ -471,17 +471,20 @@ var leapHands = (function () {
} else {
hands[h].inactiveCount += 1;
if (hands[h].inactiveCount < MAX_HAND_INACTIVE_COUNT) {
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
if (h === 0) {
MyAvatar.clearJointData("LeftHand");
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
} else {
MyAvatar.clearJointData("RightHand");
MyAvatar.clearJointData("RightForeArm");
MyAvatar.clearJointData("RightArm");
hands[h].inactiveCount += 1;
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
if (h === 0) {
MyAvatar.clearJointData("LeftHand");
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
} else {
MyAvatar.clearJointData("RightHand");
MyAvatar.clearJointData("RightForeArm");
MyAvatar.clearJointData("RightArm");
}
}
}
}

View file

@ -81,7 +81,7 @@ ExportMenu = function (opts) {
y: pos.y + margin,
width: scaleViewWidth,
height: height,
alpha: 0.0,
backgroundAlpha: 0.0,
color: { red: 255, green: 255, blue: 255 },
text: "1"
});

View file

@ -26,6 +26,7 @@ function Tooltip() {
text: "",
color: { red: 128, green: 128, blue: 128 },
alpha: 0.2,
backgroundAlpha: 0.2,
visible: false
});
this.show = function (doShow) {

View file

@ -390,6 +390,7 @@ var ZoomTool = function(opts) {
leftMargin: 4,
text: "+",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var decreaseButton = Overlays.addOverlay("text", {
@ -403,6 +404,7 @@ var ZoomTool = function(opts) {
leftMargin: 4,
text: "-",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var zoomBar = Overlays.addOverlay("text", {
@ -416,6 +418,7 @@ var ZoomTool = function(opts) {
leftMargin: 4,
text: "",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var zoomHandle = Overlays.addOverlay("text", {
@ -428,6 +431,7 @@ var ZoomTool = function(opts) {
leftMargin: 4,
text: "",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
@ -501,6 +505,7 @@ var ArrowTool = function(opts) {
leftMargin: 4,
text: "^",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var leftButton = Overlays.addOverlay("text", {
@ -514,6 +519,7 @@ var ArrowTool = function(opts) {
leftMargin: 4,
text: "<",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var rightButton = Overlays.addOverlay("text", {
@ -540,6 +546,7 @@ var ArrowTool = function(opts) {
leftMargin: 4,
text: "v",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});
var centerButton = Overlays.addOverlay("text", {
@ -553,6 +560,7 @@ var ArrowTool = function(opts) {
leftMargin: 4,
text: "",
alpha: 1.0,
backgroundAlpha: 1.0,
visible: true,
});

View file

@ -41,6 +41,8 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Entity Type:" + properties.type, type: "header" });
index++;
array.push({ label: "ID:", value: properties.id });
index++;
array.push({ label: "Locked:", type: "checkbox", value: properties.locked });
index++;
@ -265,6 +267,7 @@ EntityPropertyDialogBox = (function () {
var properties = propertiesForEditedEntity;
var index = 0;
index++; // skip type header
index++; // skip id item
properties.locked = array[index++].value;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;

File diff suppressed because it is too large Load diff

View file

@ -90,7 +90,6 @@ Grid = function(opts) {
}
that.snapToSpacing = function(delta, majorOnly) {
print('snaptogrid? ' + snapToGrid);
if (!snapToGrid) {
return delta;
}
@ -177,6 +176,8 @@ Grid = function(opts) {
color: gridColor,
alpha: gridAlpha,
});
that.emitUpdate();
}
function cleanup() {
@ -207,6 +208,7 @@ GridTool = function(opts) {
horizontalGrid.addListener(function(data) {
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
selectionDisplay.updateHandles();
});
webView.eventBridge.webEventReceived.connect(function(data) {

View file

@ -40,6 +40,7 @@ progressDialog = (function () {
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});
@ -50,6 +51,7 @@ progressDialog = (function () {
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
backgroundAlpha: 0.9,
visible: false
});

View file

@ -138,6 +138,7 @@ ToolBar = function(x, y, direction) {
width: this.width,
height: this.height,
alpha: 1.0,
backgroundAlpha: 1.0,
visible: false
});
this.spacing = [];
@ -243,7 +244,10 @@ ToolBar = function(x, y, direction) {
this.tools[tool].setAlpha(alpha);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { alpha: alpha});
Overlays.editOverlay(this.back, {
alpha: alpha,
backgroundAlpha: alpha
});
}
} else {
this.tools[tool].setAlpha(alpha);
@ -263,7 +267,7 @@ ToolBar = function(x, y, direction) {
((direction == ToolBar.VERTICAL) ? 1 : 2) * ToolBar.SPACING,
visible: true,
backgroundColor: color,
alpha: alpha
backgroundAlpha: alpha
});
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,225 +1,277 @@
//
// walkFilters.js
//
// version 1.001
//
// Created by David Wooldridge, Autumn 2014
//
// Provides a variety of filters for use by the walk.js script v1.1
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
AveragingFilter = function(length) {
//this.name = name;
this.pastValues = [];
for(var i = 0; i < length; i++) {
this.pastValues.push(0);
}
// single arg is the nextInputValue
this.process = function() {
if (this.pastValues.length === 0 && arguments[0]) {
return arguments[0];
} else if (arguments[0]) {
// apply quick and simple LP filtering
this.pastValues.push(arguments[0]);
this.pastValues.shift();
var nextOutputValue = 0;
for (var ea in this.pastValues) nextOutputValue += this.pastValues[ea];
return nextOutputValue / this.pastValues.length;
} else {
return 0;
}
};
};
// 2nd order Butterworth LP filter - calculate coeffs here: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
// provides LP filtering with a more stable frequency / phase response
ButterworthFilter = function(cutOff) {
// cut off frequency = 5Hz
this.gain = 20.20612010;
this.coeffOne = -0.4775922501;
this.coeffTwo = 1.2796324250;
// initialise the arrays
this.xv = [];
this.yv = [];
for(var i = 0; i < 3; i++) {
this.xv.push(0);
this.yv.push(0);
}
// process values
this.process = function(nextInputValue) {
this.xv[0] = this.xv[1];
this.xv[1] = this.xv[2];
this.xv[2] = nextInputValue / this.gain;
this.yv[0] = this.yv[1];
this.yv[1] = this.yv[2];
this.yv[2] = (this.xv[0] + this.xv[2]) +
2 * this.xv[1] +
(this.coeffOne * this.yv[0]) +
(this.coeffTwo * this.yv[1]);
return this.yv[2];
};
}; // end Butterworth filter contructor
// Add harmonics to a given sine wave to form square, sawtooth or triangle waves
// Geometric wave synthesis fundamentals taken from: http://hyperphysics.phy-astr.gsu.edu/hbase/audio/geowv.html
WaveSynth = function(waveShape, numHarmonics, smoothing) {
this.numHarmonics = numHarmonics;
this.waveShape = waveShape;
this.averagingFilter = new AveragingFilter(smoothing);
// NB: frequency in radians
this.shapeWave = function(frequency) {
// make some shapes
var harmonics = 0;
var multiplier = 0;
var iterations = this.numHarmonics * 2 + 2;
if (this.waveShape === TRIANGLE) {
iterations++;
}
for(var n = 2; n < iterations; n++) {
switch(this.waveShape) {
case SAWTOOTH: {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
break;
}
case TRIANGLE: {
if (n % 2 === 1) {
var mulitplier = 1 / (n * n);
// multiply (4n-1)th harmonics by -1
if (n === 3 || n === 7 || n === 11 || n === 15) {
mulitplier *= -1;
}
harmonics += mulitplier * Math.sin(n * frequency);
}
break;
}
case SQUARE: {
if (n % 2 === 1) {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
}
break;
}
}
}
// smooth the result and return
return this.averagingFilter.process(harmonics);
};
};
// Create a wave shape by summing pre-calcualted sinusoidal harmonics
HarmonicsFilter = function(magnitudes, phaseAngles) {
this.magnitudes = magnitudes;
this.phaseAngles = phaseAngles;
this.calculate = function(twoPiFT) {
var harmonics = 0;
var numHarmonics = magnitudes.length;
for(var n = 0; n < numHarmonics; n++) {
harmonics += this.magnitudes[n] * Math.cos(n * twoPiFT - this.phaseAngles[n]);
}
return harmonics;
};
};
// the main filter object literal
filter = (function() {
// Bezier private functions
function _B1(t) { return t * t * t };
function _B2(t) { return 3 * t * t * (1 - t) };
function _B3(t) { return 3 * t * (1 - t) * (1 - t) };
function _B4(t) { return (1 - t) * (1 - t) * (1 - t) };
return {
// helper methods
degToRad: function(degrees) {
var convertedValue = degrees * Math.PI / 180;
return convertedValue;
},
radToDeg: function(radians) {
var convertedValue = radians * 180 / Math.PI;
return convertedValue;
},
// these filters need instantiating, as they hold arrays of previous values
createAveragingFilter: function(length) {
var newAveragingFilter = new AveragingFilter(length);
return newAveragingFilter;
},
createButterworthFilter: function(cutoff) {
var newButterworthFilter = new ButterworthFilter(cutoff);
return newButterworthFilter;
},
createWaveSynth: function(waveShape, numHarmonics, smoothing) {
var newWaveSynth = new WaveSynth(waveShape, numHarmonics, smoothing);
return newWaveSynth;
},
createHarmonicsFilter: function(magnitudes, phaseAngles) {
var newHarmonicsFilter = new HarmonicsFilter(magnitudes, phaseAngles);
return newHarmonicsFilter;
},
// the following filters do not need separate instances, as they hold no previous values
bezier: function(percent, C1, C2, C3, C4) {
// Bezier functions for more natural transitions
// based on script by Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/
var pos = {x: 0, y: 0};
pos.x = C1.x * _B1(percent) + C2.x * _B2(percent) + C3.x * _B3(percent) + C4.x * _B4(percent);
pos.y = C1.y * _B1(percent) + C2.y * _B2(percent) + C3.y * _B3(percent) + C4.y * _B4(percent);
return pos;
},
// simple clipping filter (clips bottom of wave only, special case for hips y-axis skeleton offset)
clipTrough: function(inputValue, peak, strength) {
var outputValue = inputValue * strength;
if (outputValue < -peak) {
outputValue = -peak;
}
return outputValue;
}
}
//
// walkFilters.js
//
// version 1.002
//
// Created by David Wooldridge, Autumn 2014
//
// Provides a variety of filters for use by the walk.js script v1.12
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
AveragingFilter = function(length) {
//this.name = name;
this.pastValues = [];
for(var i = 0; i < length; i++) {
this.pastValues.push(0);
}
// single arg is the nextInputValue
this.process = function() {
if (this.pastValues.length === 0 && arguments[0]) {
return arguments[0];
} else if (arguments[0] !== null) {
// apply quick and simple LP filtering
this.pastValues.push(arguments[0]);
this.pastValues.shift();
var nextOutputValue = 0;
for (var ea in this.pastValues) nextOutputValue += this.pastValues[ea];
return nextOutputValue / this.pastValues.length;
} else {
return 0;
}
};
};
// 1st order Butterworth filter - calculate coeffs here: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
// provides LP filtering with a more stable frequency / phase response (-3 dB @ 3 Hz)
ButterworthFilter1 = function() {
this.gain = 7.313751515;
this.coeff = 0.7265425280;
// initialise the arrays
this.xv = [];
this.yv = [];
for(var i = 0; i < 2; i++) {
this.xv.push(0);
this.yv.push(0);
}
// process values
this.process = function(nextInputValue) {
this.xv[0] = this.xv[1];
this.xv[1] = nextInputValue / this.gain;
this.yv[0] = this.yv[1];
this.yv[1] = this.xv[0] + this.xv[1] + this.coeff * this.yv[0];
return this.yv[1];
};
}; // end Butterworth filter constructor
// 2nd order Butterworth LP filter - calculate coeffs here: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
// provides LP filtering with a more stable frequency / phase response
ButterworthFilter2 = function(cutOff) {
switch(cutOff) {
case 5:
default:
this.gain = 20.20612010;
this.coeffOne = -0.4775922501;
this.coeffTwo = 1.2796324250;
break;
}
// initialise the arrays
this.xv = [];
this.yv = [];
for(var i = 0; i < 3; i++) {
this.xv.push(0);
this.yv.push(0);
}
// process values
this.process = function(nextInputValue) {
this.xv[0] = this.xv[1];
this.xv[1] = this.xv[2];
this.xv[2] = nextInputValue / this.gain;
this.yv[0] = this.yv[1];
this.yv[1] = this.yv[2];
this.yv[2] = (this.xv[0] + this.xv[2]) +
2 * this.xv[1] +
(this.coeffOne * this.yv[0]) +
(this.coeffTwo * this.yv[1]);
return this.yv[2];
};
}; // end Butterworth filter constructor
// Add harmonics to a given sine wave to form square, sawtooth or triangle waves
// Geometric wave synthesis fundamentals taken from: http://hyperphysics.phy-astr.gsu.edu/hbase/audio/geowv.html
WaveSynth = function(waveShape, numHarmonics, smoothing) {
this.numHarmonics = numHarmonics;
this.waveShape = waveShape;
this.smoothingFilter = new AveragingFilter(smoothing);
// NB: frequency in radians
this.calculate = function(frequency) {
// make some shapes
var harmonics = 0;
var multiplier = 0;
var iterations = this.numHarmonics * 2 + 2;
if (this.waveShape === TRIANGLE) {
iterations++;
}
for(var n = 1; n < iterations; n++) {
switch(this.waveShape) {
case SAWTOOTH: {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
break;
}
case TRIANGLE: {
if (n % 2 === 1) {
var mulitplier = 1 / (n * n);
// multiply (4n-1)th harmonics by -1
if (n === 3 || n === 7 || n === 11 || n === 15) {
mulitplier *= -1;
}
harmonics += mulitplier * Math.sin(n * frequency);
}
break;
}
case SQUARE: {
if (n % 2 === 1) {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
}
break;
}
}
}
// smooth the result and return
return this.smoothingFilter.process(harmonics);
};
};
// Create a motion wave by summing pre-calcualted sinusoidal harmonics
HarmonicsFilter = function(magnitudes, phaseAngles) {
this.magnitudes = magnitudes;
this.phaseAngles = phaseAngles;
this.calculate = function(twoPiFT) {
var harmonics = 0;
var numHarmonics = magnitudes.length;
for(var n = 0; n < numHarmonics; n++) {
harmonics += this.magnitudes[n] * Math.cos(n * twoPiFT - this.phaseAngles[n]);
}
return harmonics;
};
};
// the main filter object
filter = (function() {
// Bezier private functions
function _B1(t) { return t * t * t };
function _B2(t) { return 3 * t * t * (1 - t) };
function _B3(t) { return 3 * t * (1 - t) * (1 - t) };
function _B4(t) { return (1 - t) * (1 - t) * (1 - t) };
return {
// helper methods
degToRad: function(degrees) {
var convertedValue = degrees * Math.PI / 180;
return convertedValue;
},
radToDeg: function(radians) {
var convertedValue = radians * 180 / Math.PI;
return convertedValue;
},
// these filters need instantiating, as they hold arrays of previous values
createAveragingFilter: function(length) {
var newAveragingFilter = new AveragingFilter(length);
return newAveragingFilter;
},
createButterworthFilter1: function() {
var newButterworthFilter = new ButterworthFilter1();
return newButterworthFilter;
},
createButterworthFilter2: function(cutoff) {
var newButterworthFilter = new ButterworthFilter2(cutoff);
return newButterworthFilter;
},
createWaveSynth: function(waveShape, numHarmonics, smoothing) {
var newWaveSynth = new WaveSynth(waveShape, numHarmonics, smoothing);
return newWaveSynth;
},
createHarmonicsFilter: function(magnitudes, phaseAngles) {
var newHarmonicsFilter = new HarmonicsFilter(magnitudes, phaseAngles);
return newHarmonicsFilter;
},
// the following filters do not need separate instances, as they hold no previous values
bezier: function(percent, C1, C2, C3, C4) {
// Bezier functions for more natural transitions
// based on script by Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/
var pos = {x: 0, y: 0};
pos.x = C1.x * _B1(percent) + C2.x * _B2(percent) + C3.x * _B3(percent) + C4.x * _B4(percent);
pos.y = C1.y * _B1(percent) + C2.y * _B2(percent) + C3.y * _B3(percent) + C4.y * _B4(percent);
return pos;
},
// simple clipping filter (clips bottom of wave only)
clipTrough: function(inputValue, peak, strength) {
var outputValue = inputValue * strength;
if (outputValue < -peak) {
outputValue = -peak;
}
return outputValue;
}
}
})();

File diff suppressed because it is too large Load diff

View file

@ -13,8 +13,8 @@ Script.include("libraries/globals.js");
var panelWall = false;
var orbShell = false;
var reticle = false;
var descriptionText = false;
var showText = false;
// used for formating the description text, in meters
var textWidth = 4;
@ -23,8 +23,6 @@ var numberOfLines = 2;
var textMargin = 0.0625;
var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines;
var lastMouseMove = 0;
var IDLE_HOVER_TIME = 2000; // if you haven't moved the mouse in 2 seconds, and in HMD mode, then we use reticle for hover
var avatarStickPosition = {};
var orbNaturalExtentsMin = { x: -1.230354, y: -1.22077, z: -1.210487 };
@ -49,6 +47,10 @@ var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8};
var HELMET_ATTACHMENT_URL = HIFI_PUBLIC_BUCKET + "models/attachments/IronManMaskOnly.fbx"
var LOBBY_PANEL_WALL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/PanelWallForInterface.fbx";
var LOBBY_BLANK_PANEL_TEXTURE_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Texture.jpg";
var LOBBY_SHELL_URL = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyShellForInterface.fbx";
var droneSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/drone.stereo.raw")
var currentDrone = null;
@ -57,13 +59,6 @@ var elevatorSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Lobby/eleva
var currentMuzakInjector = null;
var currentSound = null;
var inOculusMode = false;
function reticlePosition() {
var RETICLE_DISTANCE = 1;
return Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), RETICLE_DISTANCE));
}
function textOverlayPosition() {
var TEXT_DISTANCE_OUT = 6;
var TEXT_DISTANCE_DOWN = -2;
@ -71,6 +66,21 @@ function textOverlayPosition() {
Vec3.multiply(Quat.getUp(Camera.orientation), TEXT_DISTANCE_DOWN));
}
var panelLocationOrder = [
7, 8, 9, 10, 11, 12, 13,
0, 1, 2, 3, 4, 5, 6,
14, 15, 16, 17, 18, 19, 20
];
// Location index is 0-based
function locationIndexToPanelIndex(locationIndex) {
return panelLocationOrder.indexOf(locationIndex) + 1;
}
// Panel index is 1-based
function panelIndexToLocationIndex(panelIndex) {
return panelLocationOrder[panelIndex - 1];
}
var MAX_NUM_PANELS = 21;
var DRONE_VOLUME = 0.3;
@ -85,14 +95,14 @@ function drawLobby() {
var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT));
var panelWallProps = {
url: HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Lobby_v8/forStephen1/PanelWall2.fbx",
url: LOBBY_PANEL_WALL_URL,
position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)),
rotation: towardsMe,
dimensions: panelsDimensions
};
var orbShellProps = {
url: HIFI_PUBLIC_BUCKET + "models/sets/Lobby/Lobby_v8/forStephen1/LobbyShell1.fbx",
url: LOBBY_SHELL_URL,
position: orbPosition,
rotation: towardsMe,
dimensions: orbDimensions,
@ -113,6 +123,7 @@ function drawLobby() {
text: "",
lineHeight: lineHeight,
alpha: 0.9,
backgroundAlpha: 0.9,
ignoreRayIntersection: true,
visible: false,
isFacingAvatar: true
@ -123,22 +134,6 @@ function drawLobby() {
panelWall = Overlays.addOverlay("model", panelWallProps);
orbShell = Overlays.addOverlay("model", orbShellProps);
descriptionText = Overlays.addOverlay("text3d", descriptionTextProps);
inOculusMode = Menu.isOptionChecked("Enable VR Mode");
// for HMD wearers, create a reticle in center of screen
if (inOculusMode) {
var CURSOR_SCALE = 0.025;
reticle = Overlays.addOverlay("billboard", {
url: HIFI_PUBLIC_BUCKET + "images/cursor.svg",
position: reticlePosition(),
ignoreRayIntersection: true,
isFacingAvatar: true,
alpha: 1.0,
scale: CURSOR_SCALE
});
}
// add an attachment on this avatar so other people see them in the lobby
MyAvatar.attach(HELMET_ATTACHMENT_URL, "Neck", {x: 0, y: 0, z: 0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.15);
@ -167,7 +162,9 @@ function changeLobbyTextures() {
};
for (var j = 0; j < NUM_PANELS; j++) {
textureProp["textures"]["file" + (j + 1)] = "http:" + locations[j].thumbnail_url
var panelIndex = locationIndexToPanelIndex(j);
textureProp["textures"]["file" + panelIndex] = HIFI_PUBLIC_BUCKET + "images/locations/"
+ locations[j].id + "/hifi-location-" + locations[j].id + "_640x360.jpg";
};
Overlays.editOverlay(panelWall, textureProp);
@ -217,7 +214,7 @@ function cleanupLobby() {
panelTexturesReset["textures"] = {};
for (var j = 0; j < MAX_NUM_PANELS; j++) {
panelTexturesReset["textures"]["file" + (j + 1)] = HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyPrototype/Texture.jpg";
panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL;
};
Overlays.editOverlay(panelWall, panelTexturesReset);
@ -226,14 +223,8 @@ function cleanupLobby() {
Overlays.deleteOverlay(orbShell);
Overlays.deleteOverlay(descriptionText);
if (reticle) {
Overlays.deleteOverlay(reticle);
}
panelWall = false;
orbShell = false;
reticle = false;
Audio.stopInjector(currentDrone);
currentDrone = null;
@ -258,12 +249,13 @@ function actionStartEvent(event) {
var panelStringIndex = panelName.indexOf("Panel");
if (panelStringIndex != -1) {
var panelIndex = parseInt(panelName.slice(5)) - 1;
if (panelIndex < locations.length) {
var actionLocation = locations[panelIndex];
var panelIndex = parseInt(panelName.slice(5));
var locationIndex = panelIndexToLocationIndex(panelIndex);
if (locationIndex < locations.length) {
var actionLocation = locations[locationIndex];
print("Jumping to " + actionLocation.name + " at " + actionLocation.path
+ " in " + actionLocation.domain.name + " after click on panel " + panelIndex);
+ " in " + actionLocation.domain.name + " after click on panel " + panelIndex + " with location index " + locationIndex);
Window.location = actionLocation;
maybeCleanupLobby();
@ -307,12 +299,13 @@ function handleLookAt(pickRay) {
var panelName = result.extraInfo;
var panelStringIndex = panelName.indexOf("Panel");
if (panelStringIndex != -1) {
var panelIndex = parseInt(panelName.slice(5)) - 1;
if (panelIndex < locations.length) {
var actionLocation = locations[panelIndex];
var panelIndex = parseInt(panelName.slice(5));
var locationIndex = panelIndexToLocationIndex(panelIndex);
if (locationIndex < locations.length) {
var actionLocation = locations[locationIndex];
if (actionLocation.description == "") {
Overlays.editOverlay(descriptionText, { text: actionLocation.name, visible: true });
Overlays.editOverlay(descriptionText, { text: actionLocation.name, visible: showText });
} else {
// handle line wrapping
var allWords = actionLocation.description.split(" ");
@ -330,7 +323,7 @@ function handleLookAt(pickRay) {
} else {
currentTestLine = allWords[currentTestWord];
}
var lineLength = Overlays.textWidth(descriptionText, currentTestLine);
var lineLength = Overlays.textSize(descriptionText, currentTestLine).width;
if (lineLength < textWidth || wordsOnLine == 0) {
wordsFormated++;
currentTestWord++;
@ -344,7 +337,7 @@ function handleLookAt(pickRay) {
}
}
formatedDescription += currentGoodLine;
Overlays.editOverlay(descriptionText, { text: formatedDescription, visible: true });
Overlays.editOverlay(descriptionText, { text: formatedDescription, visible: showText });
}
} else {
Overlays.editOverlay(descriptionText, { text: "", visible: false });
@ -357,20 +350,6 @@ function handleLookAt(pickRay) {
function update(deltaTime) {
maybeCleanupLobby();
if (panelWall) {
if (reticle) {
Overlays.editOverlay(reticle, {
position: reticlePosition()
});
var nowDate = new Date();
var now = nowDate.getTime();
if (now - lastMouseMove > IDLE_HOVER_TIME) {
var pickRay = Camera.computeViewPickRay(0.5, 0.5);
handleLookAt(pickRay);
}
}
Overlays.editOverlay(descriptionText, { position: textOverlayPosition() });
// if the reticle is up then we may need to play the next muzak
@ -382,8 +361,6 @@ function update(deltaTime) {
function mouseMoveEvent(event) {
if (panelWall) {
var nowDate = new Date();
lastMouseMove = nowDate.getTime();
var pickRay = Camera.computePickRay(event.x, event.y);
handleLookAt(pickRay);
}

378
examples/notifications.js Normal file
View file

@ -0,0 +1,378 @@
//
// notifications.js
// Created by Adrian
//
// Adrian McCarlie 8-10-14
// This script demonstrates on-screen overlay type notifications.
// 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
// This script demonstrates notifications created via a number of ways, such as:
// Simple key press alerts, which only depend on a key being pressed,
// dummy examples of this are "q", "w", "e", "r", and "SPACEBAR".
// actual working examples are "a" for left turn, "d" for right turn and Ctrl/s for snapshot.
// System generated alerts such as users joining and leaving and chat messages which mention this user.
// System generated alerts which originate with a user interface event such as Window Resize, and Mic Mute/Unmute.
// Mic Mute/Unmute may appear to be a key press alert, but it actually gets the call from the system as mic is muted and unmuted,
// so the mic mute/unmute will also trigger the notification by clicking the Mic Mute button at the top of the screen.
// To add a new System notification type:
//
// 1. Set the Event Connector at the bottom of the script.
// example:
// GlobalServices.incomingMessage.connect(onIncomingMessage);
//
// 2. Create a new function to produce a text string, do not include new line returns.
// example:
// function onIncomingMessage(user, message) {
// //do stuff here;
// var text = "This is a notification";
// wordWrap(text);
// }
//
// This new function must call wordWrap(text) if the length of message is longer than 42 chars or unknown.
// wordWrap() will format the text to fit the notifications overlay and send it to createNotification(text).
// If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly.
// To add a keypress driven notification:
//
// 1. Add a key to the keyPressEvent(key).
// 2. Declare a text string.
// 3. Call createNotifications(text) parsing the text.
// example:
// if (key.text == "a") {
// var noteString = "Turning to the Left";
// createNotification(noteString);
// }
var width = 340.0; //width of notification overlay
var height = 40.0; // height of a single line notification overlay
var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window
var overlayLocationX = (windowDimensions.x - (width + 60.0));// positions window 60px from the right of the interface window
var buttonLocationX = overlayLocationX + (width - 28.0);
var locationY = 20.0; // position down from top of interface window
var topMargin = 13.0;
var leftMargin = 10.0;
var textColor = { red: 228, green: 228, blue: 228}; // text color
var backColor = { red: 38, green: 38, blue: 38}; // background color
var backgroundAlpha = 0;
var fontSize = 12.0;
var persistTime = 10.0; // time in seconds before notification fades
var clickedText = false;
var frame = 0;
var ourWidth = Window.innerWidth;
var ourHeight = Window.innerHeight;
var text = "placeholder";
var last_users = GlobalServices.onlineUsers;
var users = [];
var ctrlIsPressed = false;
var ready = true;
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
for (i = 0; i < notifications.length; i++) {
Overlays.deleteOverlay(notifications[i]);
Overlays.deleteOverlay(buttons[i]);
}
}
Script.scriptEnding.connect(scriptEnding);
var notifications = [];
var buttons = [];
var times = [];
var heights = [];
var myAlpha = [];
var arrays = [];
// This function creates and sizes the overlays
function createNotification(text) {
var count = (text.match(/\n/g) || []).length;
var breakPoint = 43.0; // length when new line is added
var extraLine = 0;
var breaks = 0;
var height = 40.0;
var stack = 0;
if (text.length >= breakPoint) {
breaks = count;
}
var extraLine = breaks * 16.0;
for (i = 0; i < heights.length; i++) {
stack = stack + heights[i];
}
var level = (stack + 20.0);
height = height + extraLine;
var overlayProperties = {
x: overlayLocationX,
y: level,
width: width,
height: height,
color: textColor,
backgroundColor: backColor,
alpha: backgroundAlpha,
backgroundAlpha: backgroundAlpha,
topMargin: topMargin,
leftMargin: leftMargin,
font: {size: fontSize},
text: text,
};
var bLevel = level + 12.0;
var buttonProperties = {
x: buttonLocationX,
y: bLevel,
width: 15.0,
height: 15.0,
subImage: { x: 0, y: 0, width: 10, height: 10 },
imageURL: "http://hifi-public.s3.amazonaws.com/images/close-small-light.svg",
color: { red: 255, green: 255, blue: 255},
visible: true,
alpha: backgroundAlpha,
};
Notify(overlayProperties, buttonProperties, height);
}
// Pushes data to each array and sets up data for 2nd dimension array
// to handle auxiliary data not carried by the overlay class
// specifically notification "heights", "times" of creation, and .
function Notify(notice, button, height){
notifications.push((Overlays.addOverlay("text", notice)));
buttons.push((Overlays.addOverlay("image",button)));
times.push(new Date().getTime() / 1000);
height = height + 1.0;
heights.push(height);
myAlpha.push(0);
var last = notifications.length - 1;
createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]);
fadeIn(notifications[last], buttons[last])
}
function fadeIn(noticeIn, buttonIn) {
var myLength = arrays.length;
var q = 0;
var pauseTimer = null;
pauseTimer = Script.setInterval(function() {
q++;
qFade = q / 10.0;
Overlays.editOverlay(noticeIn, {alpha: qFade, backgroundAlpha: qFade});
Overlays.editOverlay(buttonIn, {alpha: qFade});
if (q >= 9.0) {
Script.clearInterval(pauseTimer);
}
}, 10);
}
// push data from above to the 2 dimensional array
function createArrays(notice, button, createTime, height, myAlpha) {
arrays.push([notice, button, createTime, height, myAlpha]);
}
// handles mouse clicks on buttons
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); //identify which overlay was clicked
for (i = 0; i < buttons.length; i++) { //if user clicked a button
if(clickedOverlay == buttons[i]) {
Overlays.deleteOverlay(notifications[i]);
Overlays.deleteOverlay(buttons[i]);
notifications.splice(i, 1);
buttons.splice(i, 1);
times.splice(i, 1);
heights.splice(i, 1);
myAlpha.splice(i, 1);
arrays.splice(i, 1);
}
}
}
// Control key remains active only while key is held down
function keyReleaseEvent(key) {
if (key.key == 16777249) {
ctrlIsPressed = false;
}
}
// Triggers notification on specific key driven events
function keyPressEvent(key) {
if (key.key == 16777249) {
ctrlIsPressed = true;
}
if (key.text == "a") {
var noteString = "Turning to the Left";
createNotification(noteString);
}
if (key.text == "d") {
var noteString = "Turning to the Right";
createNotification(noteString);
}
if (key.text == "s") {
if (ctrlIsPressed == true){
var noteString = "You have taken a snapshot";
createNotification(noteString);
}
}
if (key.text == "q") {
var noteString = "Enable Scripted Motor control is now on.";
wordWrap(noteString);
}
if (key.text == "w") {
var noteString = "This notification spans 2 lines. The overlay will resize to fit new lines.";
var noteString = "editVoxels.js stopped, editModels.js stopped, selectAudioDevice.js stopped.";
wordWrap(noteString);
}
if (key.text == "e") {
var noteString = "This is an example of a multiple line notification. This notification will span 3 lines."
wordWrap(noteString);
}
if (key.text == "r") {
var noteString = "This is a very long line of text that we are going to use in this example to divide it into rows of maximum 43 chars and see how many lines we use.";
wordWrap(noteString);
}
if (key.text == "SPACE") {
var noteString = "You have pressed the Spacebar, This is an example of a multiple line notification. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.";
wordWrap(noteString);
}
}
// formats string to add newline every 43 chars
function wordWrap(str) {
var result = stringDivider(str, 43.0, "\n");
createNotification(result);
}
// wraps whole word to newline
function stringDivider(str, slotWidth, spaceReplacer) {
if (str.length > slotWidth) {
var p = slotWidth;
for (; p > 0 && str[p] != ' '; p--) {
}
if (p > 0) {
var left = str.substring(0, p);
var right = str.substring(p + 1);
return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer);
}
}
return str;
}
// This fires a notification on window resize
function checkSize(){
if((Window.innerWidth != ourWidth)||(Window.innerHeight != ourHeight)) {
var windowResize = "Window has been resized";
ourWidth = Window.innerWidth;
ourHeight = Window.innerHeight;
windowDimensions = Controller.getViewportDimensions();
overlayLocationX = (windowDimensions.x - (width + 60.0));
buttonLocationX = overlayLocationX + (width - 35.0);
createNotification(windowResize)
}
}
// Triggers notification if a user logs on or off
function onOnlineUsersChanged(users) {
var joiners = [];
var leavers = [];
for (user in users) {
if (last_users.indexOf(users[user]) == -1.0) {
joiners.push(users[user]);
createNotification(users[user] + " Has joined");
}
}
for (user in last_users) {
if (users.indexOf(last_users[user]) == -1.0) {
leavers.push(last_users[user]);
createNotification(last_users[user] + " Has left");
}
}
last_users = users;
}
// Triggers notification if @MyUserName is mentioned in chat and returns the message to the notification.
function onIncomingMessage(user, message) {
var myMessage = message;
var alertMe = "@" + GlobalServices.myUsername;
var thisAlert = user + ": " + myMessage;
if (myMessage.indexOf(alertMe) > -1.0) {
wordWrap(thisAlert);
}
}
// Triggers mic mute notification
function onMuteStateChanged() {
var muteState = AudioDevice.getMuted() ? "Muted" : "Unmuted";
var muteString = "Microphone is set to " + muteState;
createNotification(muteString);
}
function update(){
frame++;
if ((frame % 60.0) == 0) { // only update once a second
checkSize(); // checks for size change to trigger windowResize notification
locationY = 20.0;
for (var i = 0; i < arrays.length; i++) { //repositions overlays as others fade
var nextOverlay = Overlays.getOverlayAtPoint({x: overlayLocationX, y: locationY});
Overlays.editOverlay(notifications[i], { x:overlayLocationX, y:locationY});
Overlays.editOverlay(buttons[i], { x:buttonLocationX, y:locationY + 12.0});
locationY = locationY + arrays[i][3];
}
}
// This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1)
for (var i = 0; i < arrays.length; i++) {
if (ready){
var j = arrays[i][2];
var k = j + persistTime;
if (k < (new Date().getTime() / 1000)) {
ready = false;
noticeOut = arrays[i][0];
buttonOut = arrays[i][1];
var arraysOut = i;
fadeOut(noticeOut, buttonOut, arraysOut);
}
}
}
}
// this fades the notification ready for dismissal, and removes it from the arrays
function fadeOut(noticeOut, buttonOut, arraysOut) {
var myLength = arrays.length;
var r = 9.0;
var pauseTimer = null;
pauseTimer = Script.setInterval(function() {
r--;
rFade = r / 10.0;
Overlays.editOverlay(noticeOut, {alpha: rFade, backgroundAlpha: rFade});
Overlays.editOverlay(buttonOut, {alpha: rFade});
if (r < 0) {
dismiss(noticeOut, buttonOut, arraysOut);
arrays.splice(arraysOut, 1);
ready = true;
Script.clearInterval(pauseTimer);
}
}, 20);
}
// This handles the final dismissal of a notification after fading
function dismiss(firstNoteOut, firstButOut, firstOut) {
var working = firstOut
Overlays.deleteOverlay(firstNoteOut);
Overlays.deleteOverlay(firstButOut);
notifications.splice(firstOut, 1);
buttons.splice(firstOut, 1);
times.splice(firstOut, 1);
heights.splice(firstOut, 1);
myAlpha.splice(firstOut,1);
}
onMuteStateChanged();
AudioDevice.muteToggled.connect(onMuteStateChanged);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent);
GlobalServices.onlineUsersChanged.connect(onOnlineUsersChanged);
GlobalServices.incomingMessage.connect(onIncomingMessage);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.update.connect(update);

45
examples/orbitingSound.js Normal file
View file

@ -0,0 +1,45 @@
//
// orbitingSound.js
// examples
//
// Created by Philip Rosedale on December 4, 2014
// Copyright 2014 High Fidelity, Inc.
//
// An object playing a sound appears and circles you, changing brightness with the audio playing.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var RADIUS = 2.0;
var orbitCenter = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.getOrientation()), RADIUS));
var time = 0;
var SPEED = 1.0;
var currentPosition = { x: 0, y: 0, z: 0 };
var trailingLoudness = 0.0;
var soundClip = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Tabla+Loops/Tabla1.wav");
var properties = {
type: "Box",
position: orbitCenter,
dimensions: { x: 0.25, y: 0.25, z: 0.25 },
color: { red: 100, green: 0, blue : 0 }
};
var objectId = Entities.addEntity(properties);
var sound = Audio.playSound(soundClip, { position: orbitCenter, loop: true, volume: 0.5 });
function update(deltaTime) {
time += deltaTime;
currentPosition = { x: orbitCenter.x + Math.cos(time * SPEED) * RADIUS, y: orbitCenter.y, z: orbitCenter.z + Math.sin(time * SPEED) * RADIUS };
trailingLoudness = 0.9 * trailingLoudness + 0.1 * Audio.getLoudness(sound);
Entities.editEntity( objectId, { position: currentPosition, color: { red: Math.min(trailingLoudness * 2000, 255), green: 0, blue: 0 } } );
Audio.setInjectorOptions(sound, { position: currentPosition });
}
Script.scriptEnding.connect(function() {
Entities.deleteEntity(objectId);
Audio.stopInjector(sound);
});
Script.update.connect(update);

View file

@ -69,7 +69,8 @@ var text = Overlays.addOverlay("text", {
topMargin: 4,
leftMargin: 4,
text: "Here is some text.\nAnd a second line.",
alpha: 0.7
alpha: 0.7,
backgroundAlpha: 0.5
});
// This will create an image overlay, which starts out as invisible
@ -170,6 +171,7 @@ var clipboardPreview = Overlays.addOverlay("clipboard", {
// Demonstrate retrieving overlay properties
print("Text overlay text property value =\n" + Overlays.getProperty(text, "text"));
print("Text overlay alpha =\n" + Overlays.getProperty(text, "alpha"));
print("Text overlay background alpha =\n" + Overlays.getProperty(text, "backgroundAlpha"));
print("Text overlay visible =\n" + Overlays.getProperty(text, "visible"));
print("Text overlay font size =\n" + Overlays.getProperty(text, "font").size);
print("Text overlay anchor =\n" + Overlays.getProperty(text, "anchor"));

View file

@ -86,6 +86,7 @@ ChessGame.Board = (function(position, scale) {
modelURL: ChessGame.BOARD.modelURL,
position: this.position,
dimensions: this.dimensions,
rotation: ChessGame.BOARD.rotation,
userData: this.buildUserDataString()
}
this.entity = null;

View file

@ -57,6 +57,7 @@ var statusText = Overlays.addOverlay("text", {
height: 20,
backgroundColor: { red: 0, green: 0, blue: 0},
alpha: 1.0,
backgroundAlpha: 1.0,
color: { red: 255, green: 255, blue: 255},
topMargin: 4,
leftMargin: 4,

View file

@ -32,167 +32,168 @@ var clickedButton = false;
var cursor = "|";
// add more characters to the string if required
var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\
~!@#$%^&*()_+`1234567890-={}|[]\\:\";'<>?,./"; //permitted characters
~!@#$%^&*()_+`1234567890-={}|[]\\:\";'<>?,./"; //permitted characters
// This will create a text overlay that displays what you type
var inputWindow = Overlays.addOverlay("text", {
x: locationX,
y: locationY,
width: width,
height: height,
color: textColor,
backgroundColor: backColor,
alpha: backgroundAlpha,
topMargin: topMargin,
leftMargin: leftMargin,
font: {size: fontSize},
text: writing,
visible: true
});
x: locationX,
y: locationY,
width: width,
height: height,
color: textColor,
backgroundColor: backColor,
alpha: backgroundAlpha,
backgroundAlpha: backgroundAlpha,
topMargin: topMargin,
leftMargin: leftMargin,
font: {size: fontSize},
text: writing,
visible: true
});
// This will create an image overlay of a button.
var button1 = Overlays.addOverlay("image", { // green button
x: buttonLocationX,
y: locationY + 10,
width: 40,
height: 35,
subImage: { x: 0, y: 0, width: 39, height: 35 },
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
color: readyColor,
visible: true
});
x: buttonLocationX,
y: locationY + 10,
width: 40,
height: 35,
subImage: { x: 0, y: 0, width: 39, height: 35 },
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
color: readyColor,
visible: true
});
// This will create an image overlay of another button.
var button2 = Overlays.addOverlay("image", { // red button
x: buttonLocationX,
y: locationY + 60,
width: 40,
height: 35,
subImage: { x: 0, y: 0, width: 39, height: 35 },
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
color: { red: 250, green: 2, blue: 2},
visible: true,
});
x: buttonLocationX,
y: locationY + 60,
width: 40,
height: 35,
subImage: { x: 0, y: 0, width: 39, height: 35 },
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
color: { red: 250, green: 2, blue: 2},
visible: true,
});
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
Overlays.deleteOverlay(inputWindow);
Overlays.deleteOverlay(button1);
Overlays.deleteOverlay(button2);
//Return control of keys to default on script ending
for(var i=0; i<keyString.length; i++){
var nextChar = keyString.charAt(i);
Controller.releaseKeyEvents({ text: nextChar});
}
Controller.releaseKeyEvents({"key": 0x5c}); // forward slash
Controller.releaseKeyEvents({ text: "SHIFT"});
Controller.releaseKeyEvents({ text: "BACKSPACE"});
Controller.releaseKeyEvents({ text: "SPACE"});
Controller.releaseKeyEvents({"key":16777220} ); //Enter
Controller.releaseKeyEvents({"key":16777219} ); //Backspace
Overlays.deleteOverlay(inputWindow);
Overlays.deleteOverlay(button1);
Overlays.deleteOverlay(button2);
//Return control of keys to default on script ending
for(var i=0; i<keyString.length; i++){
var nextChar = keyString.charAt(i);
Controller.releaseKeyEvents({ text: nextChar});
}
Controller.releaseKeyEvents({"key": 0x5c}); // forward slash
Controller.releaseKeyEvents({ text: "SHIFT"});
Controller.releaseKeyEvents({ text: "BACKSPACE"});
Controller.releaseKeyEvents({ text: "SPACE"});
Controller.releaseKeyEvents({"key":16777220} ); //Enter
Controller.releaseKeyEvents({"key":16777219} ); //Backspace
}
Script.scriptEnding.connect(scriptEnding);
function resetForm(){
writing = ""; // Start with a blank string
Overlays.editOverlay(button1, {color: readyColor} );
Overlays.editOverlay(inputWindow, {backgroundColor: readyColor});
clickedText = true;
writing = ""; // Start with a blank string
Overlays.editOverlay(button1, {color: readyColor} );
Overlays.editOverlay(inputWindow, {backgroundColor: readyColor});
clickedText = true;
}
function submitForm(){
print("form submitted");
writingOutput = writing; // writingOutput is the data output
writing = writing + ".\nYour data has been stored.\n\nClick here to reset form or \n\n\nClick red button to close,";
Overlays.editOverlay(button1, { color: clickedColor} );
Overlays.editOverlay(inputWindow, {backgroundColor: clickedColor});
clickedText = false;
clickedButton = true;
print("form submitted");
writingOutput = writing; // writingOutput is the data output
writing = writing + ".\nYour data has been stored.\n\nClick here to reset form or \n\n\nClick red button to close,";
Overlays.editOverlay(button1, { color: clickedColor} );
Overlays.editOverlay(inputWindow, {backgroundColor: clickedColor});
clickedText = false;
clickedButton = true;
}
// handle click detection
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); //identify which overlay was clicked
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); //identify which overlay was clicked
if (clickedOverlay == inputWindow) { // if the user clicked on the text window, prepare the overlay
if (clickedText == false){ // first time clicked?
resetForm();
}
}
if (clickedOverlay == inputWindow) { // if the user clicked on the text window, prepare the overlay
if (clickedText == false){ // first time clicked?
resetForm();
}
}
if (clickedOverlay == button1) { // if the user clicked on the green button
if (clickedText == true){ // nothing happens unless the text window was clicked first
submitForm();
// clickedText == false;
}
else { // if the form has been submitted already
resetForm();
}
}
if (clickedOverlay == button1) { // if the user clicked on the green button
if (clickedText == true){ // nothing happens unless the text window was clicked first
submitForm();
// clickedText == false;
}
else { // if the form has been submitted already
resetForm();
}
}
if (clickedOverlay == button2) { // if the user clicked on the red button
print ("script ending");
Script.stop();
}
if (clickedOverlay == button2) { // if the user clicked on the red button
print ("script ending");
Script.stop();
}
}
//handle key press detection
function keyPressEvent(key) {
if (clickedText == true){
if (clickedText == true){
if (key.text == "SPACE") { //special conditions for space bar
writing = writing + " ";
key.text ="";
}
else if (key.text == "BACKSPACE") { // Backspace
var myString = writing;
writing = myString.substr(0, myString.length-1);
key.text ="";
}
if (key.text == "SPACE") { //special conditions for space bar
writing = writing + " ";
key.text ="";
}
else if (key.text == "BACKSPACE") { // Backspace
var myString = writing;
writing = myString.substr(0, myString.length-1);
key.text ="";
}
if (key.text == "\r") { //special conditions for enter key
writing = writing + "\n";
key.text ="";
}
else if ( keyString.indexOf(key.text) == -1) { // prevent all other keys not in keyString
key.text ="";
}
// build the string
writing = writing + key.text;
}
if (key.text == "\r") { //special conditions for enter key
writing = writing + "\n";
key.text ="";
}
else if ( keyString.indexOf(key.text) == -1) { // prevent all other keys not in keyString
key.text ="";
}
// build the string
writing = writing + key.text;
}
}
var count = 0;
function updateWriting(deltaTime){
count++;
// every half second or so, remove and replace the pipe to create a blinking cursor
if (count % 30 == 0) {
if (cursor == "|") {
cursor="";
} else {
cursor = "|";
}
}
// attempt at some overflow control of the text
if ((writing.length % 53) == 0) {
writing = writing + "\n";
}
// add blinking cursor to window during typing
var addCursor = writing + cursor;
if (clickedText == true){
Overlays.editOverlay(inputWindow, { text: addCursor});
}else{
Overlays.editOverlay(inputWindow, { text: writing});
}
count++;
// every half second or so, remove and replace the pipe to create a blinking cursor
if (count % 30 == 0) {
if (cursor == "|") {
cursor="";
} else {
cursor = "|";
}
}
// attempt at some overflow control of the text
if ((writing.length % 53) == 0) {
writing = writing + "\n";
}
// add blinking cursor to window during typing
var addCursor = writing + cursor;
if (clickedText == true){
Overlays.editOverlay(inputWindow, { text: addCursor});
}else{
Overlays.editOverlay(inputWindow, { text: writing});
}
}
// test keystroke against keyString and capture permitted keys
for (var i=0; i<keyString.length; i++){
var nextChar = keyString.charAt(i);
Controller.captureKeyEvents({ text: nextChar});
var nextChar = keyString.charAt(i);
Controller.captureKeyEvents({ text: nextChar});
}
// capture special keys
Controller.captureKeyEvents({ "key": 0x5c}); //forward slash key

598
examples/virtualKeyboard.js Normal file
View file

@ -0,0 +1,598 @@
//
// virtualKeyboard.js
// examples
//
// Created by Thijs Wenker on 11/18/14.
// Copyright 2014 High Fidelity, Inc.
//
// Control a virtual keyboard using your favorite HMD.
// Usage: Enable VR-mode and go to First person mode,
// look at the key that you would like to press, and press the spacebar on your "REAL" keyboard.
//
// leased some code from newEditEntities.js for Text Entity example
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("libraries/globals.js");
const KBD_UPPERCASE_DEFAULT = 0;
const KBD_LOWERCASE_DEFAULT = 1;
const KBD_UPPERCASE_HOVER = 2;
const KBD_LOWERCASE_HOVER = 3;
const KBD_BACKGROUND = 4;
const KEYBOARD_URL = HIFI_PUBLIC_BUCKET + "images/keyboard.svg";
const CURSOR_URL = HIFI_PUBLIC_BUCKET + "images/cursor.svg";
const SPACEBAR_CHARCODE = 32;
const KEYBOARD_WIDTH = 1174.7;
const KEYBOARD_HEIGHT = 434.1;
const CURSOR_WIDTH = 33.9;
const CURSOR_HEIGHT = 33.9;
// VIEW_ANGLE can be adjusted to your likings, the smaller the faster movement.
// Try setting it to 60 if it goes too fast for you.
const VIEW_ANGLE = 40.0;
const VIEW_ANGLE_BY_TWO = VIEW_ANGLE / 2;
const SPAWN_DISTANCE = 1;
const DEFAULT_TEXT_DIMENSION_Z = 0.02;
const BOUND_X = 0;
const BOUND_Y = 1;
const BOUND_W = 2;
const BOUND_H = 3;
const KEY_STATE_LOWER = 0;
const KEY_STATE_UPPER = 1;
const TEXT_MARGIN_TOP = 0.15;
const TEXT_MARGIN_LEFT = 0.15;
const TEXT_MARGIN_RIGHT = 0.17;
const TEXT_MARGIN_BOTTOM = 0.17;
var windowDimensions = Controller.getViewportDimensions();
var cursor = null;
var keyboard = new Keyboard();
var textFontSize = 9;
var text = null;
var textText = "";
var textSizeMeasureOverlay = Overlays.addOverlay("text3d", {visible: false});
function appendChar(char) {
textText += char;
updateTextOverlay();
Overlays.editOverlay(text, {text: textText});
}
function deleteChar() {
if (textText.length > 0) {
textText = textText.substring(0, textText.length - 1);
updateTextOverlay();
}
}
function updateTextOverlay() {
var textLines = textText.split("\n");
var maxLineWidth = 0;
for (textLine in textLines) {
var lineWidth = Overlays.textSize(text, textLines[textLine]).width;
if (lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
var suggestedFontSize = (windowDimensions.x / maxLineWidth) * textFontSize * 0.90;
var maxFontSize = 190 / textLines.length;
textFontSize = (suggestedFontSize > maxFontSize) ? maxFontSize : suggestedFontSize;
var topMargin = (250 - (textFontSize * textLines.length)) / 4;
Overlays.editOverlay(text, {text: textText, font: {size: textFontSize}, topMargin: topMargin});
var maxLineWidth = 0;
for (textLine in textLines) {
var lineWidth = Overlays.textSize(text, textLines[textLine]).width;
if (lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
Overlays.editOverlay(text, {leftMargin: (windowDimensions.x - maxLineWidth) / 2});
}
keyboard.onKeyPress = function(event) {
if (event.event == 'keypress') {
appendChar(event.char);
} else if (event.event == 'enter') {
appendChar("\n");
}
};
keyboard.onKeyRelease = function(event) {
print("Key release event test");
// you can cancel a key by releasing its focusing before releasing it
if (event.focus) {
if (event.event == 'delete') {
deleteChar();
} else if (event.event == 'submit') {
print(textText);
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
var textLines = textText.split("\n");
var maxLineWidth = 0;
for (textLine in textLines) {
var lineWidth = Overlays.textSize(textSizeMeasureOverlay, textLines[textLine]).width;
if (lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
var usernameLine = "--" + GlobalServices.myUsername;
var usernameWidth = Overlays.textSize(textSizeMeasureOverlay, usernameLine).width;
if (maxLineWidth < usernameWidth) {
maxLineWidth = usernameWidth;
} else {
var spaceableWidth = maxLineWidth - usernameWidth;
var spaceWidth = Overlays.textSize(textSizeMeasureOverlay, " ").width;
var numberOfSpaces = Math.floor(spaceableWidth / spaceWidth);
for (var i = 0; i < numberOfSpaces; i++) {
usernameLine = " " + usernameLine;
}
}
var dimension_x = maxLineWidth + TEXT_MARGIN_RIGHT + TEXT_MARGIN_LEFT;
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Text",
rotation: MyAvatar.orientation,
position: position,
dimensions: { x: dimension_x, y: (textLines.length + 1) * 0.14 + TEXT_MARGIN_TOP + TEXT_MARGIN_BOTTOM, z: DEFAULT_TEXT_DIMENSION_Z },
backgroundColor: { red: 0, green: 0, blue: 0 },
textColor: { red: 255, green: 255, blue: 255 },
text: textText + "\n" + usernameLine
});
}
textText = "";
updateTextOverlay();
}
}
};
keyboard.onFullyLoaded = function() {
print("Virtual-keyboard fully loaded.");
var dimensions = Controller.getViewportDimensions();
text = Overlays.addOverlay("text", {
x: 0,
y: dimensions.y - keyboard.height() - 260,
width: dimensions.x,
height: 250,
backgroundColor: { red: 255, green: 255, blue: 255},
color: { red: 0, green: 0, blue: 0},
topMargin: 5,
leftMargin: 0,
font: {size: textFontSize},
text: "",
alpha: 0.8
});
updateTextOverlay();
// the cursor is being loaded after the keyboard, else it will be on the background of the keyboard
cursor = new Cursor();
cursor.onUpdate = function(position) {
keyboard.setFocusPosition(position.x, position.y);
};
};
function KeyboardKey(keyboard, keyProperties) {
var tthis = this;
this._focus = false;
this._beingPressed = false;
this.event = keyProperties.event != undefined ?
keyProperties.event : 'keypress';
this.bounds = keyProperties.bounds;
this.states = keyProperties.states;
this.keyboard = keyboard;
this.keyState = keyProperties.keyState != undefined ? keyProperties.keyState : KBD_LOWERCASE_DEFAULT;
// one overlay per bound vector [this.bounds]
this.overlays = [];
this.getKeyEvent = function() {
if (tthis.event == 'keypress') {
var state = tthis.states[(tthis.keyboard.shift ? 1 : 2) % tthis.states.length];
return {key: state.charCode, char: state.char, event: tthis.event, focus: tthis._focus};
}
return {event: tthis.event, focus: tthis._focus};
};
this.containsCoord = function(x, y) {
for (var i = 0; i < this.bounds.length; i++) {
if (x >= this.bounds[i][BOUND_X] &&
x <= (this.bounds[i][BOUND_X] + this.bounds[i][BOUND_W]) &&
y >= this.bounds[i][BOUND_Y] &&
y <= (this.bounds[i][BOUND_Y] + this.bounds[i][BOUND_H]))
{
return true;
}
}
return false;
};
this.updateState = function() {
tthis.setState(eval('KBD_' + (tthis.keyboard.shift ? 'UPPERCASE' : 'LOWERCASE') + '_' + (tthis._focus ? 'HOVER' : 'DEFAULT')));
};
this.updateColor = function() {
var colorIntensity = this._beingPressed ? 128 : 255;
for (var i = 0; i < tthis.bounds.length; i++) {
Overlays.editOverlay(tthis.overlays[i],
{color: {red: colorIntensity, green: colorIntensity, blue: colorIntensity}}
);
}
};
this.press = function() {
tthis._beingPressed = true;
tthis.updateColor();
};
this.release = function() {
tthis._beingPressed = false;
tthis.updateColor();
};
this.blur = function() {
tthis._focus = false;
tthis.updateState();
};
this.focus = function() {
tthis._focus = true;
tthis.updateState();
};
this.setState = function(state) {
tthis.keyState = state;
for (var i = 0; i < tthis.bounds.length; i++) {
Overlays.editOverlay(tthis.overlays[i], {
subImage: {width: tthis.bounds[i][BOUND_W], height: tthis.bounds[i][BOUND_H], x: tthis.bounds[i][BOUND_X], y: (KEYBOARD_HEIGHT * tthis.keyState) + tthis.bounds[i][BOUND_Y]}
});
}
};
this.rescale = function() {
for (var i = 0; i < tthis.bounds.length; i++) {
Overlays.editOverlay(tthis.overlays[i], {
x: tthis.keyboard.getX() + tthis.bounds[i][BOUND_X] * keyboard.scale,
y: tthis.keyboard.getY() + tthis.bounds[i][BOUND_Y] * keyboard.scale,
width: this.bounds[i][BOUND_W] * keyboard.scale,
height: this.bounds[i][BOUND_H] * keyboard.scale
});
}
};
this.remove = function() {
for (var i = 0; i < this.overlays.length; i++) {
Overlays.deleteOverlay(this.overlays[i]);
}
};
this.isLoaded = function() {
for (var i = 0; i < this.overlays.length; i++) {
if (!Overlays.isLoaded(this.overlays[i])) {
return false;
}
}
return true;
};
for (var i = 0; i < this.bounds.length; i++) {
var newOverlay = Overlays.cloneOverlay(this.keyboard.background);
Overlays.editOverlay(newOverlay, {
x: this.keyboard.getX() + this.bounds[i][BOUND_X] * keyboard.scale,
y: this.keyboard.getY() + this.bounds[i][BOUND_Y] * keyboard.scale,
width: this.bounds[i][BOUND_W] * keyboard.scale,
height: this.bounds[i][BOUND_H] * keyboard.scale,
subImage: {width: this.bounds[i][BOUND_W], height: this.bounds[i][BOUND_H], x: this.bounds[i][BOUND_X], y: (KEYBOARD_HEIGHT * this.keyState) + this.bounds[i][BOUND_Y]},
alpha: 1
});
this.overlays.push(newOverlay);
}
}
function Keyboard() {
var tthis = this;
this.focussed_key = -1;
this.scale = windowDimensions.x / KEYBOARD_WIDTH;
this.shift = false;
this.width = function() {
return KEYBOARD_WIDTH * tthis.scale;
};
this.height = function() {
return KEYBOARD_HEIGHT * tthis.scale;
};
this.getX = function() {
return (windowDimensions.x / 2) - (this.width() / 2);
};
this.getY = function() {
return windowDimensions.y - this.height();
};
this.background = Overlays.addOverlay("image", {
x: this.getX(),
y: this.getY(),
width: this.width(),
height: this.height(),
subImage: {width: KEYBOARD_WIDTH, height: KEYBOARD_HEIGHT, y: KEYBOARD_HEIGHT * KBD_BACKGROUND},
imageURL: KEYBOARD_URL,
alpha: 1
});
this.rescale = function() {
this.scale = windowDimensions.x / KEYBOARD_WIDTH;
Overlays.editOverlay(tthis.background, {
x: this.getX(),
y: this.getY(),
width: this.width(),
height: this.height()
});
for (var i = 0; i < tthis.keys.length; i++) {
tthis.keys[i].rescale();
}
};
this.setFocusPosition = function(x, y) {
// set to local unscaled position
var localx = (x - tthis.getX()) / tthis.scale;
var localy = (y - tthis.getY()) / tthis.scale;
var new_focus_key = -1;
if (localx >= 0 && localy >= 0 && localx <= KEYBOARD_WIDTH && localy <= KEYBOARD_HEIGHT) {
for (var i = 0; i < tthis.keys.length; i++) {
if (tthis.keys[i].containsCoord(localx, localy)) {
new_focus_key = i;
break;
}
}
}
if (new_focus_key != tthis.focussed_key) {
if (tthis.focussed_key != -1) {
tthis.keys[tthis.focussed_key].blur();
}
tthis.focussed_key = new_focus_key;
if (tthis.focussed_key != -1) {
tthis.keys[tthis.focussed_key].focus();
}
}
return tthis;
};
this.pressFocussedKey = function() {
if (tthis.focussed_key != -1) {
if (tthis.keys[tthis.focussed_key].event == 'shift') {
tthis.toggleShift();
} else {
tthis.keys[tthis.focussed_key].press();
}
if (this.onKeyPress != null) {
this.onKeyPress(tthis.keys[tthis.focussed_key].getKeyEvent());
}
}
return tthis;
};
this.releaseKeys = function() {
for (var i = 0; i < tthis.keys.length; i++) {
if (tthis.keys[i]._beingPressed) {
if (tthis.keys[i].event != 'shift') {
tthis.keys[i].release();
}
if (this.onKeyRelease != null) {
this.onKeyRelease(tthis.keys[i].getKeyEvent());
}
}
}
};
this.toggleShift = function() {
tthis.shift = !tthis.shift;
for (var i = 0; i < tthis.keys.length; i++) {
tthis.keys[i].updateState();
if (tthis.keys[i].event == 'shift') {
if (tthis.shift) {
tthis.keys[i].press();
continue;
}
tthis.keys[i].release();
}
}
};
this.getFocussedKey = function() {
if (tthis.focussed_key == -1) {
return null;
}
return tthis.keys[tthis.focussed_key];
};
this.remove = function() {
Overlays.deleteOverlay(this.background);
for (var i = 0; i < this.keys.length; i++) {
this.keys[i].remove();
}
};
this.onKeyPress = null;
this.onKeyRelease = null;
this.onSubmit = null;
this.onFullyLoaded = null;
this.keys = [];
//
// keyProperties contains the key data
//
// coords [[x,y,w,h],[x,y,w,h]]
// states array of 1 or 2 objects [lowercase, uppercase] each object contains a charCode and a char
var keyProperties = [
{bounds: [[12, 12, 65, 52]], states: [{charCode: 126, char: '~'}]},
{bounds: [[84, 12, 65, 52]], states: [{charCode: 33, char: '!'}]},
{bounds: [[156, 12, 65, 52]], states: [{charCode: 64, char: '@'}]},
{bounds: [[228, 12, 65, 52]], states: [{charCode: 35, char: '#'}]},
{bounds: [[300, 12, 65, 52]], states: [{charCode: 36, char: '$'}]},
{bounds: [[372, 12, 65, 52]], states: [{charCode: 37, char: '%'}]},
{bounds: [[445, 12, 65, 52]], states: [{charCode: 94, char: '^'}]},
{bounds: [[517, 12, 65, 52]], states: [{charCode: 38, char: '&'}]},
{bounds: [[589, 12, 65, 52]], states: [{charCode: 42, char: '*'}]},
{bounds: [[662, 12, 65, 52]], states: [{charCode: 40, char: '('}]},
{bounds: [[734, 12, 65, 52]], states: [{charCode: 41, char: ')'}]},
{bounds: [[806, 12, 65, 52]], states: [{charCode: 95, char: '_'}]},
{bounds: [[881, 12, 65, 52]], states: [{charCode: 123, char: '{'}]},
{bounds: [[953, 12, 65, 52]], states: [{charCode: 125, char: '}'}]},
{bounds: [[1025, 12, 65, 52]], states: [{charCode: 60, char: '<'}]},
{bounds: [[1097, 12, 65, 52]], states: [{charCode: 62, char: '>'}]},
{bounds: [[12, 71, 65, 63]], states: [{charCode: 96, char: '`'}]},
{bounds: [[84, 71, 65, 63]], states: [{charCode: 49, char: '1'}]},
{bounds: [[156, 71, 65, 63]], states: [{charCode: 50, char: '2'}]},
{bounds: [[228, 71, 65, 63]], states: [{charCode: 51, char: '3'}]},
{bounds: [[300, 71, 65, 63]], states: [{charCode: 52, char: '4'}]},
{bounds: [[372, 71, 65, 63]], states: [{charCode: 53, char: '5'}]},
{bounds: [[445, 71, 65, 63]], states: [{charCode: 54, char: '6'}]},
{bounds: [[517, 71, 65, 63]], states: [{charCode: 55, char: '7'}]},
{bounds: [[589, 71, 65, 63]], states: [{charCode: 56, char: '8'}]},
{bounds: [[661, 71, 65, 63]], states: [{charCode: 57, char: '9'}]},
{bounds: [[733, 71, 65, 63]], states: [{charCode: 48, char: '0'}]},
{bounds: [[806, 71, 65, 63]], states: [{charCode: 45, char: '-'}]},
{bounds: [[880, 71, 65, 63]], states: [{charCode: 61, char: '='}]},
{bounds: [[953, 71, 65, 63]], states: [{charCode: 43, char: '+'}]},
{bounds: [[1024, 71, 139, 63]], event: 'delete'},
// enter key has 2 bounds and one state
{bounds: [[11, 143, 98, 71], [11, 213, 121, 62]], event: 'enter'},
{bounds: [[118, 142, 64, 63]], states: [{charCode: 113, char: 'q'}, {charCode: 81, char: 'Q'}]},
{bounds: [[190, 142, 64, 63]], states: [{charCode: 119, char: 'w'}, {charCode: 87, char: 'W'}]},
{bounds: [[262, 142, 64, 63]], states: [{charCode: 101, char: 'e'}, {charCode: 69, char: 'E'}]},
{bounds: [[334, 142, 64, 63]], states: [{charCode: 114, char: 'r'}, {charCode: 82, char: 'R'}]},
{bounds: [[407, 142, 64, 63]], states: [{charCode: 116, char: 't'}, {charCode: 84, char: 'T'}]},
{bounds: [[479, 142, 64, 63]], states: [{charCode: 121, char: 'y'}, {charCode: 89, char: 'Y'}]},
{bounds: [[551, 142, 65, 63]], states: [{charCode: 117, char: 'u'}, {charCode: 85, char: 'U'}]},
{bounds: [[623, 142, 65, 63]], states: [{charCode: 105, char: 'i'}, {charCode: 73, char: 'I'}]},
{bounds: [[695, 142, 65, 63]], states: [{charCode: 111, char: 'o'}, {charCode: 79, char: 'O'}]},
{bounds: [[768, 142, 64, 63]], states: [{charCode: 112, char: 'p'}, {charCode: 80, char: 'P'}]},
{bounds: [[840, 142, 64, 63]], states: [{charCode: 91, char: '['}]},
{bounds: [[912, 142, 65, 63]], states: [{charCode: 93, char: ']'}]},
{bounds: [[984, 142, 65, 63]], states: [{charCode: 92, char: '\\'}]},
{bounds: [[1055, 142, 65, 63]], states: [{charCode: 124, char: '|'}]},
{bounds: [[1126, 143, 35, 72], [1008, 214, 153, 62]], event: 'enter'},
{bounds: [[140, 213, 65, 63]], states: [{charCode: 97, char: 'a'}, {charCode: 65, char: 'A'}]},
{bounds: [[211, 213, 64, 63]], states: [{charCode: 115, char: 's'}, {charCode: 83, char: 'S'}]},
{bounds: [[283, 213, 65, 63]], states: [{charCode: 100, char: 'd'}, {charCode: 68, char: 'D'}]},
{bounds: [[355, 213, 65, 63]], states: [{charCode: 102, char: 'f'}, {charCode: 70, char: 'F'}]},
{bounds: [[428, 213, 64, 63]], states: [{charCode: 103, char: 'g'}, {charCode: 71, char: 'G'}]},
{bounds: [[500, 213, 64, 63]], states: [{charCode: 104, char: 'h'}, {charCode: 72, char: 'H'}]},
{bounds: [[572, 213, 65, 63]], states: [{charCode: 106, char: 'j'}, {charCode: 74, char: 'J'}]},
{bounds: [[644, 213, 65, 63]], states: [{charCode: 107, char: 'k'}, {charCode: 75, char: 'K'}]},
{bounds: [[716, 213, 65, 63]], states: [{charCode: 108, char: 'l'}, {charCode: 76, char: 'L'}]},
{bounds: [[789, 213, 64, 63]], states: [{charCode: 59, char: ';'}]},
{bounds: [[861, 213, 64, 63]], states: [{charCode: 39, char: '\''}]},
{bounds: [[934, 213, 65, 63]], states: [{charCode: 58, char: ':'}]},
{bounds: [[12, 283, 157, 63]], event: 'shift'},
{bounds: [[176, 283, 65, 63]], states: [{charCode: 122, char: 'z'}, {charCode: 90, char: 'Z'}]},
{bounds: [[249, 283, 64, 63]], states: [{charCode: 120, char: 'x'}, {charCode: 88, char: 'X'}]},
{bounds: [[321, 283, 64, 63]], states: [{charCode: 99, char: 'c'}, {charCode: 67, char: 'C'}]},
{bounds: [[393, 283, 64, 63]], states: [{charCode: 118, char: 'v'}, {charCode: 86, char: 'V'}]},
{bounds: [[465, 283, 65, 63]], states: [{charCode: 98, char: 'b'}, {charCode: 66, char: 'B'}]},
{bounds: [[537, 283, 65, 63]], states: [{charCode: 110, char: 'n'}, {charCode: 78, char: 'N'}]},
{bounds: [[610, 283, 64, 63]], states: [{charCode: 109, char: 'm'}, {charCode: 77, char: 'M'}]},
{bounds: [[682, 283, 64, 63]], states: [{charCode: 44, char: ','}]},
{bounds: [[754, 283, 65, 63]], states: [{charCode: 46, char: '.'}]},
{bounds: [[826, 283, 65, 63]], states: [{charCode: 47, char: '/'}]},
{bounds: [[899, 283, 64, 63]], states: [{charCode: 63, char: '?'}]},
{bounds: [[972, 283, 190, 63]], event: 'shift'},
{bounds: [[249, 355, 573, 67]], states: [{charCode: 32, char: ' '}]},
{bounds: [[899, 355, 263, 67]], event: 'submit'}
];
this.keyboardTextureLoaded = function() {
if (Overlays.isLoaded(tthis.background)) {
Script.clearInterval(tthis.keyboardTextureLoaded_timer);
for (var i = 0; i < keyProperties.length; i++) {
tthis.keys.push(new KeyboardKey(tthis, keyProperties[i]));
}
if (keyboard.onFullyLoaded != null) {
tthis.onFullyLoaded();
}
}
};
this.keyboardTextureLoaded_timer = Script.setInterval(this.keyboardTextureLoaded, 250);
}
function Cursor() {
var tthis = this;
this.x = windowDimensions.x / 2;
this.y = windowDimensions.y / 2;
this.overlay = Overlays.addOverlay("image", {
x: this.x,
y: this.y,
width: CURSOR_WIDTH,
height: CURSOR_HEIGHT,
imageURL: CURSOR_URL,
alpha: 1
});
this.remove = function() {
Overlays.deleteOverlay(this.overlay);
};
this.getPosition = function() {
return {x: tthis.getX(), y: tthis.getY()};
};
this.getX = function() {
return tthis.x;
};
this.getY = function() {
return tthis.y;
};
this.onUpdate = null;
this.update = function() {
var newWindowDimensions = Controller.getViewportDimensions();
if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) {
windowDimensions = newWindowDimensions;
keyboard.rescale();
Overlays.editOverlay(text, {
y: windowDimensions.y - keyboard.height() - 260,
width: windowDimensions.x
});
}
var editobject = {};
if (MyAvatar.getHeadFinalYaw() <= VIEW_ANGLE_BY_TWO && MyAvatar.getHeadFinalYaw() >= -1 * VIEW_ANGLE_BY_TWO) {
angle = ((-1 * MyAvatar.getHeadFinalYaw()) + VIEW_ANGLE_BY_TWO) / VIEW_ANGLE;
tthis.x = angle * windowDimensions.x;
editobject.x = tthis.x - (CURSOR_WIDTH / 2);
}
if (MyAvatar.getHeadFinalPitch() <= VIEW_ANGLE_BY_TWO && MyAvatar.getHeadFinalPitch() >= -1 * VIEW_ANGLE_BY_TWO) {
angle = ((-1 * MyAvatar.getHeadFinalPitch()) + VIEW_ANGLE_BY_TWO) / VIEW_ANGLE;
tthis.y = angle * windowDimensions.y;
editobject.y = tthis.y - (CURSOR_HEIGHT / 2);
}
if (Object.keys(editobject).length > 0) {
Overlays.editOverlay(tthis.overlay, editobject);
if (tthis.onUpdate != null) {
tthis.onUpdate(tthis.getPosition());
}
}
};
Script.update.connect(this.update);
}
function keyPressEvent(event) {
if (event.key === SPACEBAR_CHARCODE) {
keyboard.pressFocussedKey();
}
}
function keyReleaseEvent(event) {
if (event.key === SPACEBAR_CHARCODE) {
keyboard.releaseKeys();
}
}
function scriptEnding() {
keyboard.remove();
cursor.remove();
Overlays.deleteOverlay(text);
Overlays.deleteOverlay(textSizeMeasureOverlay);
Controller.releaseKeyEvents({key: SPACEBAR_CHARCODE});
}
Controller.captureKeyEvents({key: SPACEBAR_CHARCODE});
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.scriptEnding.connect(scriptEnding);

File diff suppressed because it is too large Load diff

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 "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "Gverb")
set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "PrioVR" "Sixense" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "Gverb")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -107,7 +107,8 @@ endif()
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
# link required hifi libraries
link_hifi_libraries(shared octree voxels fbx metavoxels networking entities avatars audio animation script-engine physics)
link_hifi_libraries(shared octree voxels gpu fbx metavoxels networking entities avatars audio animation script-engine physics
render-utils entities-renderer)
# find any optional and required libraries
find_package(ZLIB REQUIRED)
@ -156,7 +157,6 @@ if (VISAGE_FOUND AND NOT DISABLE_VISAGE AND APPLE)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment")
find_library(AVFoundation AVFoundation)
find_library(CoreMedia CoreMedia)
find_library(NEW_STD_LIBRARY libc++.dylib /usr/lib/)
target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY})
endif ()

View file

@ -12,4 +12,4 @@ Clément Brisset, October 22nd, 2014
3. Place the directories “include” and “src” in interface/external/gverb
(Normally next to this readme)
4. Clear your build directory, run cmake, build and you should be all set.
4. Clear your build directory, run cmake, build and you should be all set.

View file

@ -36,25 +36,36 @@ uniform vec2 depthTexCoordOffset;
uniform vec2 depthTexCoordScale;
void main(void) {
float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r;
vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st);
vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st);
// compute the view space position using the depth
float z = near / (texture2D(depthMap, gl_TexCoord[0].st).r * depthScale - 1.0);
float z = near / (depthVal * depthScale - 1.0);
vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 0.0);
// get the normal from the map
vec4 normal = texture2D(normalMap, gl_TexCoord[0].st);
vec4 normalizedNormal = normalize(normal * 2.0 - vec4(1.0, 1.0, 1.0, 2.0));
// compute the base color based on OpenGL lighting model
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
float facingLight = step(0.0, diffuse);
vec4 baseColor = texture2D(diffuseMap, gl_TexCoord[0].st) * (gl_FrontLightModelProduct.sceneColor +
gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
// compute the specular multiplier (sans exponent)
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(position)),
normalizedNormal));
// add specular contribution
vec4 specularColor = texture2D(specularMap, gl_TexCoord[0].st);
gl_FragColor = vec4(baseColor.rgb + pow(specular, specularColor.a * 128.0) * specularColor.rgb, normal.a);
vec4 normal = normalVal;
if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) {
normal.a = 1.0;
normalVal.a = 0.0;
gl_FragColor = vec4(diffuseVal.rgb * specularVal.rgb, 1.0);
} else {
vec3 normalizedNormal = normalize(normal.xyz * 2.0 - vec3(1.0));
// compute the base color based on OpenGL lighting model
float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz);
float facingLight = step(0.0, diffuse);
vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb +
gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight));
// compute the specular multiplier (sans exponent)
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)),
normalizedNormal));
// add specular contribution
vec4 specularColor = specularVal;
gl_FragColor = vec4(baseColor.rgb + pow(specular, specularColor.a * 128.0) * specularColor.rgb, normal.a);
}
}

View file

@ -11,6 +11,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const int MAX_TEXCOORDS = 2;
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
// the interpolated normal
varying vec4 normal;
@ -22,7 +26,7 @@ void main(void) {
gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
// use standard pipeline transform
gl_Position = ftransform();

View file

@ -0,0 +1,37 @@
#version 120
//
// model_lightmap.frag
// fragment shader
//
// Created by Samuel Gateau on 11/19/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
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the emissive map texture and parameters
uniform sampler2D emissiveMap;
uniform vec2 emissiveParams;
// the alpha threshold
uniform float alphaThreshold;
// the interpolated normal
varying vec4 normal;
// the interpolated texcoord1
varying vec2 interpolatedTexcoord1;
void main(void) {
// set the diffuse, normal, specular data
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st);
gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold)));
gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);
gl_FragData[2] = vec4((vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), gl_FrontMaterial.shininess / 128.0);
}

View file

@ -0,0 +1,40 @@
#version 120
//
// model_lightmap.vert
// vertex shader
//
// Created by Sam Gateau on 11/21/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
//
const int MAX_TEXCOORDS = 2;
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
attribute vec2 texcoord1;
// the interpolated normal
varying vec4 normal;
// the interpolated texcoord1
varying vec2 interpolatedTexcoord1;
void main(void) {
// transform and store the normal for interpolation
normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0));
// pass along the diffuse color
gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
interpolatedTexcoord1 = vec2(texcoordMatrices[1] * vec4(texcoord1.xy, 0.0, 1.0)).xy;
// use standard pipeline transform
gl_Position = ftransform();
}

View file

@ -0,0 +1,50 @@
#version 120
//
// model_lightmap_normal_map.frag
// fragment shader
//
// Created by Samuel Gateau on 11/19/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
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the normal map texture
uniform sampler2D normalMap;
// the emissive map texture and parameters
uniform sampler2D emissiveMap;
uniform vec2 emissiveParams;
// the alpha threshold
uniform float alphaThreshold;
// the interpolated normal
varying vec4 interpolatedNormal;
// the interpolated tangent
varying vec4 interpolatedTangent;
varying vec2 interpolatedTexcoord1;
void main(void) {
// compute the view normal from the various bits
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
vec3 normalizedTangent = normalize(vec3(interpolatedTangent));
vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent));
vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) - vec3(0.5, 0.5, 0.5);
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
// set the diffuse, normal, specular data
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st);
gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold)));
gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0);
gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb, gl_FrontMaterial.shininess / 128.0);
}

View file

@ -0,0 +1,46 @@
#version 120
//
// model_lightmap_normal_map.vert
// vertex shader
//
// Created by Sam Gateau on 11/21/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
//
const int MAX_TEXCOORDS = 2;
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
// the tangent vector
attribute vec3 tangent;
attribute vec2 texcoord1;
// the interpolated normal
varying vec4 interpolatedNormal;
// the interpolated tangent
varying vec4 interpolatedTangent;
// the interpolated texcoord1
varying vec2 interpolatedTexcoord1;
void main(void) {
// transform and store the normal and tangent for interpolation
interpolatedNormal = gl_ModelViewMatrix * vec4(gl_Normal, 0.0);
interpolatedTangent = gl_ModelViewMatrix * vec4(tangent, 0.0);
// pass along the diffuse color
gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
interpolatedTexcoord1 = vec2(texcoordMatrices[1] * vec4(texcoord1.xy, 0.0, 1.0)).xy;
// use standard pipeline transform
gl_Position = ftransform();
}

View file

@ -0,0 +1,54 @@
#version 120
//
// model_lightmap_normal_specular_map.frag
// fragment shader
//
// Created by Samuel Gateau on 11/19/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 diffuse texture
uniform sampler2D diffuseMap;
// the emissive map texture and parameters
uniform sampler2D emissiveMap;
uniform vec2 emissiveParams;
// the normal map texture
uniform sampler2D normalMap;
// the specular map texture
uniform sampler2D specularMap;
// the alpha threshold
uniform float alphaThreshold;
// the interpolated normal
varying vec4 interpolatedNormal;
// the interpolated tangent
varying vec4 interpolatedTangent;
varying vec2 interpolatedTexcoord1;
void main(void) {
// compute the view normal from the various bits
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
vec3 normalizedTangent = normalize(vec3(interpolatedTangent));
vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent));
vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) - vec3(0.5, 0.5, 0.5);
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
// set the diffuse, normal, specular data
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st);
gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold)));
gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0);
gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb,
gl_FrontMaterial.shininess / 128.0);
}

View file

@ -0,0 +1,40 @@
#version 120
//
// model_lightmap_specular_map.frag
// fragment shader
//
// Created by Samuel Gateau on 11/19/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 diffuse texture
uniform sampler2D diffuseMap;
// the emissive map texture and parameters
uniform sampler2D emissiveMap;
uniform vec2 emissiveParams;
// the specular texture
uniform sampler2D specularMap;
// the alpha threshold
uniform float alphaThreshold;
// the interpolated normal
varying vec4 normal;
varying vec2 interpolatedTexcoord1;
void main(void) {
// set the diffuse, normal, specular data
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st);
gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold)));
gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb,
gl_FrontMaterial.shininess / 128.0);
}

View file

@ -11,6 +11,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const int MAX_TEXCOORDS = 2;
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
// the tangent vector
attribute vec3 tangent;
@ -29,7 +33,7 @@ void main(void) {
gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
// use standard pipeline transform
gl_Position = ftransform();

View file

@ -1,21 +0,0 @@
#version 120
//
// passthrough.vert
// vertex shader
//
// 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
//
attribute float voxelSizeIn;
varying float voxelSize;
void main(void) {
gl_FrontColor = gl_Color;
gl_Position = gl_Vertex; // don't call ftransform(), because we do projection in geometry shader
voxelSize = voxelSizeIn;
}

View file

@ -1,174 +0,0 @@
#version 120
//
// point_size.vert
// vertex shader
//
// 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
//
attribute float voxelSizeIn;
varying float voxelSize;
uniform float viewportWidth;
uniform float viewportHeight;
uniform vec3 cameraPosition;
// Bit codes for faces
const int NONE = 0;
const int RIGHT = 1;
const int LEFT = 2;
const int BOTTOM = 4;
const int BOTTOM_RIGHT = BOTTOM + RIGHT;
const int BOTTOM_LEFT = BOTTOM + LEFT;
const int TOP = 8;
const int TOP_RIGHT = TOP + RIGHT;
const int TOP_LEFT = TOP + LEFT;
const int NEAR = 16;
const int NEAR_RIGHT = NEAR + RIGHT;
const int NEAR_LEFT = NEAR + LEFT;
const int NEAR_BOTTOM = NEAR + BOTTOM;
const int NEAR_BOTTOM_RIGHT = NEAR + BOTTOM + RIGHT;
const int NEAR_BOTTOM_LEFT = NEAR + BOTTOM + LEFT;
const int NEAR_TOP = NEAR + TOP;
const int NEAR_TOP_RIGHT = NEAR + TOP + RIGHT;
const int NEAR_TOP_LEFT = NEAR + TOP + LEFT;
const int FAR = 32;
const int FAR_RIGHT = FAR + RIGHT;
const int FAR_LEFT = FAR + LEFT;
const int FAR_BOTTOM = FAR + BOTTOM;
const int FAR_BOTTOM_RIGHT = FAR + BOTTOM + RIGHT;
const int FAR_BOTTOM_LEFT = FAR + BOTTOM + LEFT;
const int FAR_TOP = FAR + TOP;
const int FAR_TOP_RIGHT = FAR + TOP + RIGHT;
const int FAR_TOP_LEFT = FAR + TOP + LEFT;
// If we know the position of the camera relative to the voxel, we can a priori know the vertices that make the visible hull
// polygon. This also tells us which two vertices are known to make the longest possible distance between any pair of these
// vertices for the projected polygon. This is a visibleFaces table based on this knowledge.
void main(void) {
// Note: the gl_Vertex in this case are in "world coordinates" meaning they've already been scaled to TREE_SCALE
// this is also true for voxelSizeIn.
vec4 bottomNearRight = gl_Vertex;
vec4 topFarLeft = (gl_Vertex + vec4(voxelSizeIn, voxelSizeIn, voxelSizeIn, 0.0));
int visibleFaces = NONE;
// In order to use our visibleFaces "table" (implemented as if statements) below, we need to encode the 6-bit code to
// orient camera relative to the 6 defining faces of the voxel. Based on camera position relative to the bottomNearRight
// corner and the topFarLeft corner, we can calculate which hull and therefore which two vertices are furthest apart
// linearly once projected
if (cameraPosition.x < bottomNearRight.x) {
visibleFaces += RIGHT;
}
if (cameraPosition.x > topFarLeft.x) {
visibleFaces += LEFT;
}
if (cameraPosition.y < bottomNearRight.y) {
visibleFaces += BOTTOM;
}
if (cameraPosition.y > topFarLeft.y) {
visibleFaces += TOP;
}
if (cameraPosition.z < bottomNearRight.z) {
visibleFaces += NEAR;
}
if (cameraPosition.z > topFarLeft.z) {
visibleFaces += FAR;
}
vec4 cornerAdjustOne;
vec4 cornerAdjustTwo;
if (visibleFaces == RIGHT) {
cornerAdjustOne = vec4(0,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(0,1,1,0) * voxelSizeIn;
} else if (visibleFaces == LEFT) {
cornerAdjustOne = vec4(1,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,1,0) * voxelSizeIn;
} else if (visibleFaces == BOTTOM) {
cornerAdjustOne = vec4(0,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,0,1,0) * voxelSizeIn;
} else if (visibleFaces == TOP) {
cornerAdjustOne = vec4(0,1,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,1,0) * voxelSizeIn;
} else if (visibleFaces == NEAR) {
cornerAdjustOne = vec4(0,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,0,0) * voxelSizeIn;
} else if (visibleFaces == FAR) {
cornerAdjustOne = vec4(0,0,1,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,1,0) * voxelSizeIn;
} else if (visibleFaces == NEAR_BOTTOM_LEFT ||
visibleFaces == FAR_TOP ||
visibleFaces == FAR_TOP_RIGHT) {
cornerAdjustOne = vec4(0,1,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,0,1,0) * voxelSizeIn;
} else if (visibleFaces == FAR_TOP_LEFT ||
visibleFaces == NEAR_RIGHT ||
visibleFaces == NEAR_BOTTOM ||
visibleFaces == NEAR_BOTTOM_RIGHT) {
cornerAdjustOne = vec4(0,0,1,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,0,0) * voxelSizeIn;
} else if (visibleFaces == NEAR_TOP_RIGHT ||
visibleFaces == FAR_LEFT ||
visibleFaces == FAR_BOTTOM_LEFT ||
visibleFaces == BOTTOM_RIGHT ||
visibleFaces == TOP_LEFT) {
cornerAdjustOne = vec4(1,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(0,1,1,0) * voxelSizeIn;
// Everything else...
//} else if (visibleFaces == BOTTOM_LEFT ||
// visibleFaces == TOP_RIGHT ||
// visibleFaces == NEAR_LEFT ||
// visibleFaces == FAR_RIGHT ||
// visibleFaces == NEAR_TOP ||
// visibleFaces == NEAR_TOP_LEFT ||
// visibleFaces == FAR_BOTTOM ||
// visibleFaces == FAR_BOTTOM_RIGHT) {
} else {
cornerAdjustOne = vec4(0,0,0,0) * voxelSizeIn;
cornerAdjustTwo = vec4(1,1,1,0) * voxelSizeIn;
}
// Determine our corners
vec4 cornerOne = gl_Vertex + cornerAdjustOne;
vec4 cornerTwo = gl_Vertex + cornerAdjustTwo;
// Find their model view projections
vec4 cornerOneMVP = gl_ModelViewProjectionMatrix * cornerOne;
vec4 cornerTwoMVP = gl_ModelViewProjectionMatrix * cornerTwo;
// Map to x, y screen coordinates
vec2 cornerOneScreen = vec2(cornerOneMVP.x / cornerOneMVP.w, cornerOneMVP.y / cornerOneMVP.w);
if (cornerOneMVP.w < 0) {
cornerOneScreen.x = -cornerOneScreen.x;
cornerOneScreen.y = -cornerOneScreen.y;
}
vec2 cornerTwoScreen = vec2(cornerTwoMVP.x / cornerTwoMVP.w, cornerTwoMVP.y / cornerTwoMVP.w);
if (cornerTwoMVP.w < 0) {
cornerTwoScreen.x = -cornerTwoScreen.x;
cornerTwoScreen.y = -cornerTwoScreen.y;
}
// Find the distance between them in pixels
float voxelScreenWidth = abs(cornerOneScreen.x - cornerTwoScreen.x) * viewportWidth / 2.0;
float voxelScreenHeight = abs(cornerOneScreen.y - cornerTwoScreen.y) * viewportHeight / 2.0;
float voxelScreenLength = sqrt(voxelScreenHeight * voxelScreenHeight + voxelScreenWidth * voxelScreenWidth);
// Find the center of the voxel
vec4 centerVertex = gl_Vertex;
float halfSizeIn = voxelSizeIn / 2;
centerVertex += vec4(halfSizeIn, halfSizeIn, halfSizeIn, 0.0);
vec4 center = gl_ModelViewProjectionMatrix * centerVertex;
// Finally place the point at the center of the voxel, with a size equal to the maximum screen length
gl_Position = center;
gl_PointSize = voxelScreenLength;
gl_FrontColor = gl_Color;
}

View file

@ -11,10 +11,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const int MAX_TEXCOORDS = 2;
const int MAX_CLUSTERS = 128;
const int INDICES_PER_VERTEX = 4;
uniform mat4 clusterMatrices[MAX_CLUSTERS];
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
attribute vec4 clusterIndices;
attribute vec4 clusterWeights;
@ -38,7 +40,7 @@ void main(void) {
gl_FrontColor = gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * position;
}

View file

@ -11,10 +11,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const int MAX_TEXCOORDS = 2;
const int MAX_CLUSTERS = 128;
const int INDICES_PER_VERTEX = 4;
uniform mat4 clusterMatrices[MAX_CLUSTERS];
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
// the tangent vector
attribute vec3 tangent;
@ -46,7 +48,7 @@ void main(void) {
gl_FrontColor = gl_FrontMaterial.diffuse;
// and the texture coordinates
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * interpolatedPosition;
}

View file

@ -1,87 +0,0 @@
#version 120
#extension GL_ARB_geometry_shader4 : enable
//
// voxel.geom
// geometry shader
//
// 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
//
//
// VOXEL GEOMETRY SHADER
//
// Input: gl_VerticesIn/gl_PositionIn
// GL_POINTS
// Assumes vertex shader has not transformed coordinates
// Each gl_PositionIn is the corner of voxel
// varying voxelSize - which is the voxel size
//
// Note: In vertex shader doesn't do any transform. Therefore passing the 3D world coordinates xyz to us
//
// Output: GL_TRIANGLE_STRIP
//
// Issues:
// how do we need to handle lighting of these colors??
// how do we handle normals?
// check for size=0 and don't output the primitive
//
varying in float voxelSize[1];
const int VERTICES_PER_FACE = 4;
const int COORD_PER_VERTEX = 3;
const int COORD_PER_FACE = COORD_PER_VERTEX * VERTICES_PER_FACE;
void faceOfVoxel(vec4 corner, float scale, float[COORD_PER_FACE] facePoints, vec4 color, vec4 normal) {
for (int v = 0; v < VERTICES_PER_FACE; v++ ) {
vec4 vertex = corner;
for (int c = 0; c < COORD_PER_VERTEX; c++ ) {
int cIndex = c + (v * COORD_PER_VERTEX);
vertex[c] += (facePoints[cIndex] * scale);
}
gl_FrontColor = color * (gl_LightModel.ambient + gl_LightSource[0].ambient +
gl_LightSource[0].diffuse * max(0.0, dot(normal, gl_LightSource[0].position)));
gl_Position = gl_ModelViewProjectionMatrix * vertex;
EmitVertex();
}
EndPrimitive();
}
void main() {
//increment variable
int i;
vec4 corner;
float scale;
float bottomFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1 );
float topFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1 );
float rightFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1 );
float leftFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1 );
float frontFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 );
float backFace[COORD_PER_FACE] = float[COORD_PER_FACE]( 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1 );
vec4 bottomNormal = vec4(0.0, -1, 0.0, 0.0);
vec4 topNormal = vec4(0.0, 1, 0.0, 0.0);
vec4 rightNormal = vec4( -1, 0.0, 0.0, 0.0);
vec4 leftNormal = vec4( 1, 0.0, 0.0, 0.0);
vec4 frontNormal = vec4(0.0, 0.0, -1, 0.0);
vec4 backNormal = vec4(0.0, 0.0, 1, 0.0);
for(i = 0; i < gl_VerticesIn; i++) {
corner = gl_PositionIn[i];
scale = voxelSize[i];
faceOfVoxel(corner, scale, bottomFace, gl_FrontColorIn[i], bottomNormal);
faceOfVoxel(corner, scale, topFace, gl_FrontColorIn[i], topNormal );
faceOfVoxel(corner, scale, rightFace, gl_FrontColorIn[i], rightNormal );
faceOfVoxel(corner, scale, leftFace, gl_FrontColorIn[i], leftNormal );
faceOfVoxel(corner, scale, frontFace, gl_FrontColorIn[i], frontNormal );
faceOfVoxel(corner, scale, backFace, gl_FrontColorIn[i], backNormal );
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,37 +12,33 @@
#ifndef hifi_Application_h
#define hifi_Application_h
#include <map>
#include <time.h>
#include <gpu/GPUConfig.h>
#include <QApplication>
#include <QMainWindow>
#include <QAction>
#include <QHash>
#include <QImage>
#include <QList>
#include <QPointer>
#include <QSet>
#include <QSettings>
#include <QStringList>
#include <QHash>
#include <QTouchEvent>
#include <QUndoStack>
#include <QSystemTrayIcon>
#include <EntityEditPacketSender.h>
#include <AbstractScriptingServicesInterface.h>
#include <AbstractViewStateInterface.h>
#include <EntityCollisionSystem.h>
#include <EntityEditPacketSender.h>
#include <EntityTreeRenderer.h>
#include <GeometryCache.h>
#include <NetworkPacket.h>
#include <NodeList.h>
#include <OctreeQuery.h>
#include <PacketHeaders.h>
#include <ScriptEngine.h>
#include <OctreeQuery.h>
#include <TextureCache.h>
#include <ViewFrustum.h>
#include <VoxelEditPacketSender.h>
#include "MainWindow.h"
#include "Audio.h"
#include "AudioReflector.h"
#include "Camera.h"
#include "DatagramProcessor.h"
#include "Environment.h"
@ -55,22 +51,12 @@
#include "avatar/Avatar.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "devices/Faceshift.h"
#include "devices/PrioVR.h"
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
#include "devices/DdeFaceTracker.h"
#include "entities/EntityTreeRenderer.h"
#include "renderer/AmbientOcclusionEffect.h"
#include "renderer/DeferredLightingEffect.h"
#include "renderer/GeometryCache.h"
#include "renderer/GlowEffect.h"
#include "renderer/PointShader.h"
#include "renderer/TextureCache.h"
#include "renderer/VoxelShader.h"
#include "scripting/ControllerScriptingInterface.h"
#include "ui/BandwidthDialog.h"
#include "ui/BandwidthMeter.h"
#include "ui/HMDToolsDialog.h"
#include "ui/ModelsBrowser.h"
#include "ui/NodeBounds.h"
#include "ui/OctreeStatsDialog.h"
@ -94,16 +80,19 @@
#include "UndoStackScriptingInterface.h"
class QAction;
class QActionGroup;
class QGLWidget;
class QKeyEvent;
class QMouseEvent;
class QSettings;
class QSystemTrayIcon;
class QTouchEvent;
class QWheelEvent;
class FaceTracker;
class MainWindow;
class Node;
class ProgramObject;
class ScriptEngine;
static const float NODE_ADDED_RED = 0.0f;
static const float NODE_ADDED_GREEN = 1.0f;
@ -131,7 +120,7 @@ static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS
static const QString INFO_HELP_PATH = "html/interface-welcome-allsvg.html";
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html";
class Application : public QApplication {
class Application : public QApplication, public AbstractViewStateInterface, AbstractScriptingServicesInterface {
Q_OBJECT
friend class OctreePacketProcessor;
@ -140,7 +129,6 @@ class Application : public QApplication {
public:
static Application* getInstance() { return static_cast<Application*>(QCoreApplication::instance()); }
static QString& resourcesPath();
static const glm::vec3& getPositionForPath() { return getInstance()->_myAvatar->getPosition(); }
static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); }
@ -187,42 +175,50 @@ public:
void removeVoxel(glm::vec3 position, float scale);
glm::vec3 getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVoxel);
bool isThrottleRendering() const { return DependencyManager::get<GLCanvas>()->isThrottleRendering(); }
GLCanvas* getGLWidget() { return _glWidget; }
bool isThrottleRendering() const { return _glWidget->isThrottleRendering(); }
MyAvatar* getAvatar() { return _myAvatar; }
const MyAvatar* getAvatar() const { return _myAvatar; }
Audio* getAudio() { return &_audio; }
const AudioReflector* getAudioReflector() const { return &_audioReflector; }
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
ViewFrustum* getDisplayViewFrustum() { return &_displayViewFrustum; }
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
VoxelImporter* getVoxelImporter() { return &_voxelImporter; }
VoxelSystem* getVoxels() { return &_voxels; }
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
MetavoxelSystem* getMetavoxels() { return &_metavoxels; }
EntityTreeRenderer* getEntities() { return &_entities; }
bool getImportSucceded() { return _importSucceded; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
Environment* getEnvironment() { return &_environment; }
PrioVR* getPrioVR() { return &_prioVR; }
QUndoStack* getUndoStack() { return &_undoStack; }
MainWindow* getWindow() { return _window; }
VoxelImporter* getVoxelImporter() { return &_voxelImporter; }
VoxelTree* getClipboard() { return &_clipboard; }
EntityTree* getEntityClipboard() { return &_entityClipboard; }
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
Environment* getEnvironment() { return &_environment; }
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
bool getImportSucceded() { return _importSucceded; }
bool isMousePressed() const { return _mousePressed; }
bool isMouseHidden() const { return _mouseHidden; }
bool isMouseHidden() const { return DependencyManager::get<GLCanvas>()->cursor().shape() == Qt::BlankCursor; }
void setCursorVisible(bool visible);
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
int getMouseX() const { return _mouseX; }
int getMouseY() const { return _mouseY; }
bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated;; }
Faceshift* getFaceshift() { return &_faceshift; }
Visage* getVisage() { return &_visage; }
DdeFaceTracker* getDDE() { return &_dde; }
bool mouseOnScreen() const;
int getMouseX() const;
int getMouseY() const;
int getTrueMouseX() const { return DependencyManager::get<GLCanvas>()->mapFromGlobal(QCursor::pos()).x(); }
int getTrueMouseY() const { return DependencyManager::get<GLCanvas>()->mapFromGlobal(QCursor::pos()).y(); }
int getMouseDragStartedX() const;
int getMouseDragStartedY() const;
int getTrueMouseDragStartedX() const { return _mouseDragStartedX; }
int getTrueMouseDragStartedY() const { return _mouseDragStartedY; }
bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated; }
FaceTracker* getActiveFaceTracker();
PrioVR* getPrioVR() { return &_prioVR; }
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
QUndoStack* getUndoStack() { return &_undoStack; }
QSystemTrayIcon* getTrayIcon() { return _trayIcon; }
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
Overlays& getOverlays() { return _overlays; }
@ -233,7 +229,7 @@ public:
const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; }
void setViewMatrixTranslation(const glm::vec3& translation) { _viewMatrixTranslation = translation; }
const Transform& getViewTransform() const { return _viewTransform; }
virtual const Transform& getViewTransform() const { return _viewTransform; }
void setViewTransform(const Transform& view);
/// if you need to access the application settings, use lockSettings()/unlockSettings()
@ -242,26 +238,23 @@ public:
void saveSettings();
MainWindow* getWindow() { return _window; }
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }
ToolWindow* getToolWindow() { return _toolWindow ; }
GeometryCache* getGeometryCache() { return &_geometryCache; }
AnimationCache* getAnimationCache() { return &_animationCache; }
TextureCache* getTextureCache() { return &_textureCache; }
DeferredLightingEffect* getDeferredLightingEffect() { return &_deferredLightingEffect; }
GlowEffect* getGlowEffect() { return &_glowEffect; }
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
virtual AbstractControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
AvatarManager& getAvatarManager() { return _avatarManager; }
void resetProfile(const QString& username);
void controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes);
void setupWorldLight();
virtual void setupWorldLight();
virtual bool shouldRenderMesh(float largestDimension, float distanceToCamera);
QImage renderAvatarBillboard();
@ -280,19 +273,27 @@ public:
void getModelViewMatrix(glm::dmat4* modelViewMatrix);
void getProjectionMatrix(glm::dmat4* projectionMatrix);
const glm::vec3& getShadowDistances() const { return _shadowDistances; }
virtual const glm::vec3& getShadowDistances() const { return _shadowDistances; }
/// Computes the off-axis frustum parameters for the view frustum, taking mirroring into account.
void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
virtual void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const;
virtual ViewFrustum* getCurrentViewFrustum() { return getDisplayViewFrustum(); }
virtual bool getShadowsEnabled();
virtual bool getCascadeShadowsEnabled();
virtual QThread* getMainThread() { return thread(); }
virtual float getSizeScale() const;
virtual int getBoundaryLevelAdjust() const;
virtual PickRay computePickRay(float x, float y);
virtual const glm::vec3& getAvatarPosition() const { return getAvatar()->getPosition(); }
NodeBounds& getNodeBoundsDisplay() { return _nodeBoundsDisplay; }
VoxelShader& getVoxelShader() { return _voxelShader; }
PointShader& getPointShader() { return _pointShader; }
FileLogger* getLogger() { return _logger; }
glm::vec2 getViewportDimensions() const { return glm::vec2(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); }
glm::vec2 getViewportDimensions() const { return glm::vec2(DependencyManager::get<GLCanvas>()->getDeviceWidth(),
DependencyManager::get<GLCanvas>()->getDeviceHeight()); }
NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; }
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination);
@ -301,8 +302,6 @@ public:
QStringList getRunningScripts() { return _scriptEnginesHash.keys(); }
ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; }
void setCursorVisible(bool visible);
bool isLookingAtMyAvatar(Avatar* avatar);
@ -313,13 +312,13 @@ public:
bool isVSyncEditable() const;
bool isAboutToQuit() const { return _aboutToQuit; }
void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
// the isHMDmode is true whenever we use the interface from an HMD and not a standard flat display
// rendering of several elements depend on that
// TODO: carry that information on the Camera as a setting
bool isHMDMode() const;
QRect getDesirableApplicationGeometry();
RunningScriptsWidget* getRunningScriptsWidget() { return _runningScriptsWidget; }
signals:
@ -392,9 +391,13 @@ private slots:
void timer();
void idle();
void aboutToQuit();
void handleScriptEngineLoaded(const QString& scriptFilename);
void handleScriptLoadError(const QString& scriptFilename);
void connectedToDomain(const QString& hostname);
friend class HMDToolsDialog;
void setFullscreen(bool fullscreen);
void setEnable3DTVMode(bool enable3DTVMode);
void setEnableVRMode(bool enableVRMode);
@ -465,7 +468,6 @@ private:
int sendNackPackets();
MainWindow* _window;
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
ToolWindow* _toolWindow;
@ -524,10 +526,6 @@ private:
AvatarManager _avatarManager;
MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
Faceshift _faceshift;
Visage _visage;
DdeFaceTracker _dde;
PrioVR _prioVR;
Camera _myCamera; // My view onto the world
@ -551,14 +549,10 @@ private:
Environment _environment;
int _mouseX;
int _mouseY;
int _mouseDragStartedX;
int _mouseDragStartedY;
quint64 _lastMouseMove;
bool _lastMouseMoveWasSimulated;
bool _mouseHidden;
bool _seenMouseMove;
glm::vec3 _mouseRayOrigin;
glm::vec3 _mouseRayDirection;
@ -573,17 +567,6 @@ private:
QSet<int> _keysPressed;
GeometryCache _geometryCache;
AnimationCache _animationCache;
TextureCache _textureCache;
DeferredLightingEffect _deferredLightingEffect;
GlowEffect _glowEffect;
AmbientOcclusionEffect _ambientOcclusionEffect;
VoxelShader _voxelShader;
PointShader _pointShader;
Audio _audio;
bool _enableProcessVoxelsThread;
@ -628,7 +611,6 @@ private:
Overlays _overlays;
ApplicationOverlay _applicationOverlay;
AudioReflector _audioReflector;
RunningScriptsWidget* _runningScriptsWidget;
QHash<QString, ScriptEngine*> _scriptEnginesHash;
bool _runningScriptsWidgetWasVisible;

View file

@ -37,6 +37,7 @@
#include <AudioInjector.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <PathUtils.h>
#include <SharedUtil.h>
#include <StDev.h>
#include <UUID.h>
@ -103,10 +104,6 @@ Audio::Audio(QObject* parent) :
_gverb(NULL),
_iconColor(1.0f),
_iconPulseTimeReference(usecTimestampNow()),
_processSpatialAudio(false),
_spatialAudioStart(0),
_spatialAudioFinish(0),
_spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, true), // random access mode
_scopeEnabled(false),
_scopeEnabledPause(false),
_scopeInputOffset(0),
@ -144,9 +141,9 @@ Audio::Audio(QObject* parent) :
}
void Audio::init(QGLWidget *parent) {
_micTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/mic.svg"));
_muteTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/mic-mute.svg"));
_boxTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/audio-box.svg"));
_micTextureId = parent->bindTexture(QImage(PathUtils::resourcesPath() + "images/mic.svg"));
_muteTextureId = parent->bindTexture(QImage(PathUtils::resourcesPath() + "images/mic-mute.svg"));
_boxTextureId = parent->bindTexture(QImage(PathUtils::resourcesPath() + "images/audio-box.svg"));
}
void Audio::reset() {
@ -454,7 +451,9 @@ void Audio::start() {
qDebug() << "Unable to set up audio output because of a problem with output format.";
}
_inputFrameBuffer.initialize( _inputFormat.channelCount(), _audioInput->bufferSize() * 8 );
if (_audioInput) {
_inputFrameBuffer.initialize( _inputFormat.channelCount(), _audioInput->bufferSize() * 8 );
}
_inputGain.initialize();
_sourceGain.initialize();
_noiseSource.initialize();
@ -838,13 +837,6 @@ void Audio::handleAudioInput() {
_lastInputLoudness = 0;
}
// at this point we have clean monoAudioSamples, which match our target output...
// this is what we should send to our interested listeners
if (_processSpatialAudio && !_muted && !_isStereoInput && _audioOutput) {
QByteArray monoInputData((char*)networkAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * sizeof(int16_t));
emit processLocalAudio(_spatialAudioStart, monoInputData, _desiredInputFormat);
}
if (!_isStereoInput && _proceduralAudioOutput) {
processProceduralAudio(networkAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
}
@ -984,34 +976,11 @@ void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& ou
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
const int16_t* receivedSamples;
if (_processSpatialAudio) {
unsigned int sampleTime = _spatialAudioStart;
QByteArray buffer = inputBuffer;
// copy the samples we'll resample from the ring buffer - this also
// pushes the read pointer of the ring buffer forwards
//receivedAudioStreamPopOutput.readSamples(receivedSamples, numNetworkOutputSamples);
// Accumulate direct transmission of audio from sender to receiver
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples);
}
// Send audio off for spatial processing
emit processInboundAudio(sampleTime, buffer, _desiredOutputFormat);
// copy the samples we'll resample from the spatial audio ring buffer - this also
// pushes the read pointer of the spatial audio ring buffer forwards
_spatialAudioRingBuffer.readSamples(_outputProcessingBuffer, numNetworkOutputSamples);
// Advance the start point for the next packet of audio to arrive
_spatialAudioStart += numNetworkOutputSamples / _desiredOutputFormat.channelCount();
receivedSamples = _outputProcessingBuffer;
} else {
// copy the samples we'll resample from the ring buffer - this also
// pushes the read pointer of the ring buffer forwards
//receivedAudioStreamPopOutput.readSamples(receivedSamples, numNetworkOutputSamples);
receivedSamples = reinterpret_cast<const int16_t*>(inputBuffer.data());
}
receivedSamples = reinterpret_cast<const int16_t*>(inputBuffer.data());
// copy the packet from the RB to the output
linearResampling(receivedSamples,
@ -1122,68 +1091,7 @@ void Audio::sendDownstreamAudioStatsPacket() {
nodeList->writeDatagram(packet, dataAt - packet, audioMixer);
}
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
// data we know that we will have 2x samples for each stereo time sample at the format's sample rate
void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
// Calculate the number of remaining samples available. The source spatial audio buffer will get
// clipped if there are insufficient samples available in the accumulation buffer.
unsigned int remaining = _spatialAudioRingBuffer.getSampleCapacity() - _spatialAudioRingBuffer.samplesAvailable();
// Locate where in the accumulation buffer the new samples need to go
if (sampleTime >= _spatialAudioFinish) {
if (_spatialAudioStart == _spatialAudioFinish) {
// Nothing in the spatial audio ring buffer yet, Just do a straight copy, clipping if necessary
unsigned int sampleCount = (remaining < numSamples) ? remaining : numSamples;
if (sampleCount) {
_spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data(), sampleCount);
}
_spatialAudioFinish = _spatialAudioStart + sampleCount / _desiredOutputFormat.channelCount();
} else {
// Spatial audio ring buffer already has data, but there is no overlap with the new sample.
// Compute the appropriate time delay and pad with silence until the new start time.
unsigned int delay = sampleTime - _spatialAudioFinish;
unsigned int delayCount = delay * _desiredOutputFormat.channelCount();
unsigned int silentCount = (remaining < delayCount) ? remaining : delayCount;
if (silentCount) {
_spatialAudioRingBuffer.addSilentSamples(silentCount);
}
// Recalculate the number of remaining samples
remaining -= silentCount;
unsigned int sampleCount = (remaining < numSamples) ? remaining : numSamples;
// Copy the new spatial audio to the accumulation ring buffer
if (sampleCount) {
_spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data(), sampleCount);
}
_spatialAudioFinish += (sampleCount + silentCount) / _desiredOutputFormat.channelCount();
}
} else {
// There is overlap between the spatial audio buffer and the new sample, mix the overlap
// Calculate the offset from the buffer's current read position, which should be located at _spatialAudioStart
unsigned int offset = (sampleTime - _spatialAudioStart) * _desiredOutputFormat.channelCount();
unsigned int mixedSamplesCount = (_spatialAudioFinish - sampleTime) * _desiredOutputFormat.channelCount();
mixedSamplesCount = (mixedSamplesCount < numSamples) ? mixedSamplesCount : numSamples;
const int16_t* spatial = reinterpret_cast<const int16_t*>(spatialAudio.data());
for (unsigned int i = 0; i < mixedSamplesCount; i++) {
int existingSample = _spatialAudioRingBuffer[i + offset];
int newSample = spatial[i];
int sumOfSamples = existingSample + newSample;
_spatialAudioRingBuffer[i + offset] = static_cast<int16_t>(glm::clamp<int>(sumOfSamples,
std::numeric_limits<short>::min(), std::numeric_limits<short>::max()));
}
// Copy the remaining unoverlapped spatial audio to the spatial audio buffer, if any
unsigned int nonMixedSampleCount = numSamples - mixedSamplesCount;
nonMixedSampleCount = (remaining < nonMixedSampleCount) ? remaining : nonMixedSampleCount;
if (nonMixedSampleCount) {
_spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data() + mixedSamplesCount, nonMixedSampleCount);
// Extend the finish time by the amount of unoverlapped samples
_spatialAudioFinish += nonMixedSampleCount / _desiredOutputFormat.channelCount();
}
}
}
bool Audio::mousePressEvent(int x, int y) {
if (_iconBounds.contains(x, y)) {
@ -1263,15 +1171,6 @@ void Audio::selectAudioSourceSine440() {
_noiseSourceEnabled = !_toneSourceEnabled;
}
void Audio::toggleAudioSpatialProcessing() {
_processSpatialAudio = !_processSpatialAudio;
if (_processSpatialAudio) {
_spatialAudioStart = 0;
_spatialAudioFinish = 0;
_spatialAudioRingBuffer.reset();
}
}
// Take a pointer to the acquired microphone input samples and add procedural sounds
void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
float sample;
@ -1436,7 +1335,7 @@ void Audio::renderToolBox(int x, int y, bool boxed) {
static const float PULSE_MAX = 1.0f;
static const float PULSE_FREQUENCY = 1.0f; // in Hz
qint64 now = usecTimestampNow();
if (now - _iconPulseTimeReference > USECS_PER_SECOND) {
if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) {
// Prevents t from getting too big, which would diminish glm::cos precision
_iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND);
}
@ -1993,11 +1892,6 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
_timeSinceLastReceived.start();
// setup spatial audio ringbuffer
int numFrameSamples = _outputFormat.sampleRate() * _desiredOutputFormat.channelCount();
_spatialAudioRingBuffer.resizeForFrameSize(numFrameSamples);
_spatialAudioStart = _spatialAudioFinish = 0;
supportedFormat = true;
}
}
@ -2044,6 +1938,9 @@ int Audio::calculateNumberOfFrameSamples(int numBytes) const {
}
float Audio::getAudioOutputMsecsUnplayed() const {
if (!_audioOutput) {
return 0.0f;
}
int bytesAudioOutputUnplayed = _audioOutput->bufferSize() - _audioOutput->bytesFree();
float msecsAudioOutputUnplayed = bytesAudioOutputUnplayed / (float)_outputFormat.bytesForDuration(USECS_PER_MSEC);
return msecsAudioOutputUnplayed;

View file

@ -115,8 +115,6 @@ public:
int getNetworkSampleRate() { return SAMPLE_RATE; }
int getNetworkBufferLengthSamplesPerChannel() { return NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; }
bool getProcessSpatialAudio() const { return _processSpatialAudio; }
float getInputRingBufferMsecsAvailable() const;
float getInputRingBufferAverageMsecsAvailable() const { return (float)_inputRingBufferMsecsAvailableStats.getWindowAverage(); }
@ -131,7 +129,6 @@ public slots:
void addReceivedAudioToStream(const QByteArray& audioByteArray);
void parseAudioStreamStatsPacket(const QByteArray& packet);
void parseAudioEnvironmentData(const QByteArray& packet);
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
void handleAudioInput();
void reset();
void resetStats();
@ -145,7 +142,6 @@ public slots:
void toggleScopePause();
void toggleStats();
void toggleStatsShowInjectedStreams();
void toggleAudioSpatialProcessing();
void toggleStereoInput();
void selectAudioScopeFiveFrames();
void selectAudioScopeTwentyFrames();
@ -254,11 +250,6 @@ private:
float _iconColor;
qint64 _iconPulseTimeReference;
bool _processSpatialAudio; /// Process received audio by spatial audio hooks
unsigned int _spatialAudioStart; /// Start of spatial audio interval (in sample rate time base)
unsigned int _spatialAudioFinish; /// End of spatial audio interval (in sample rate time base)
AudioRingBuffer _spatialAudioRingBuffer; /// Spatially processed audio
// Process procedural audio by
// 1. Echo to the local procedural output device
// 2. Mix with the audio input

View file

@ -1,858 +0,0 @@
//
// AudioReflector.cpp
// interface
//
// Created by Brad Hefta-Gaub on 4/2/2014
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <QMutexLocker>
#include "AudioReflector.h"
#include "Menu.h"
const float DEFAULT_PRE_DELAY = 20.0f; // this delay in msecs will always be added to all reflections
const float DEFAULT_MS_DELAY_PER_METER = 3.0f;
const float MINIMUM_ATTENUATION_TO_REFLECT = 1.0f / 256.0f;
const float DEFAULT_DISTANCE_SCALING_FACTOR = 2.0f;
const float MAXIMUM_DELAY_MS = 1000.0 * 20.0f; // stop reflecting after path is this long
const int DEFAULT_DIFFUSION_FANOUT = 5;
const unsigned int ABSOLUTE_MAXIMUM_BOUNCE_COUNT = 10;
const float DEFAULT_LOCAL_ATTENUATION_FACTOR = 0.125;
const float DEFAULT_COMB_FILTER_WINDOW = 0.05f; //ms delay differential to avoid
const float SLIGHTLY_SHORT = 0.999f; // slightly inside the distance so we're on the inside of the reflection point
const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbed
const float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused
const float DEFAULT_ORIGINAL_ATTENUATION = 1.0f;
const float DEFAULT_ECHO_ATTENUATION = 1.0f;
AudioReflector::AudioReflector(QObject* parent) :
QObject(parent),
_preDelay(DEFAULT_PRE_DELAY),
_soundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),
_distanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR),
_localAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),
_combFilterWindow(DEFAULT_COMB_FILTER_WINDOW),
_diffusionFanout(DEFAULT_DIFFUSION_FANOUT),
_absorptionRatio(DEFAULT_ABSORPTION_RATIO),
_diffusionRatio(DEFAULT_DIFFUSION_RATIO),
_originalSourceAttenuation(DEFAULT_ORIGINAL_ATTENUATION),
_allEchoesAttenuation(DEFAULT_ECHO_ATTENUATION),
_withDiffusion(false),
_lastPreDelay(DEFAULT_PRE_DELAY),
_lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),
_lastDistanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR),
_lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),
_lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT),
_lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO),
_lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO),
_lastDontDistanceAttenuate(false),
_lastAlternateDistanceAttenuate(false)
{
_reflections = 0;
_diffusionPathCount = 0;
_officialAverageAttenuation = _averageAttenuation = 0.0f;
_officialMaxAttenuation = _maxAttenuation = 0.0f;
_officialMinAttenuation = _minAttenuation = 0.0f;
_officialAverageDelay = _averageDelay = 0;
_officialMaxDelay = _maxDelay = 0;
_officialMinDelay = _minDelay = 0;
_inboundEchoesCount = 0;
_inboundEchoesSuppressedCount = 0;
_localEchoesCount = 0;
_localEchoesSuppressedCount = 0;
}
bool AudioReflector::haveAttributesChanged() {
bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions);
bool dontDistanceAttenuate = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingDontDistanceAttenuate);
bool alternateDistanceAttenuate = Menu::getInstance()->isOptionChecked(
MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate);
bool attributesChange = (_withDiffusion != withDiffusion
|| _lastPreDelay != _preDelay
|| _lastSoundMsPerMeter != _soundMsPerMeter
|| _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor
|| _lastDiffusionFanout != _diffusionFanout
|| _lastAbsorptionRatio != _absorptionRatio
|| _lastDiffusionRatio != _diffusionRatio
|| _lastDontDistanceAttenuate != dontDistanceAttenuate
|| _lastAlternateDistanceAttenuate != alternateDistanceAttenuate);
if (attributesChange) {
_withDiffusion = withDiffusion;
_lastPreDelay = _preDelay;
_lastSoundMsPerMeter = _soundMsPerMeter;
_lastDistanceAttenuationScalingFactor = _distanceAttenuationScalingFactor;
_lastDiffusionFanout = _diffusionFanout;
_lastAbsorptionRatio = _absorptionRatio;
_lastDiffusionRatio = _diffusionRatio;
_lastDontDistanceAttenuate = dontDistanceAttenuate;
_lastAlternateDistanceAttenuate = alternateDistanceAttenuate;
}
return attributesChange;
}
void AudioReflector::render() {
// if we're not set up yet, or we're not processing spatial audio, then exit early
if (!_myAvatar || !_audio->getProcessSpatialAudio()) {
return;
}
// use this oportunity to calculate our reflections
calculateAllReflections();
// only render if we've been asked to do so
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingRenderPaths)) {
drawRays();
}
}
// delay = 1ms per foot
// = 3ms per meter
float AudioReflector::getDelayFromDistance(float distance) {
float delay = (_soundMsPerMeter * distance);
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay)) {
delay += _preDelay;
}
return delay;
}
// attenuation = from the Audio Mixer
float AudioReflector::getDistanceAttenuationCoefficient(float distance) {
bool doDistanceAttenuation = !Menu::getInstance()->isOptionChecked(
MenuOption::AudioSpatialProcessingDontDistanceAttenuate);
bool originalFormula = !Menu::getInstance()->isOptionChecked(
MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate);
float distanceCoefficient = 1.0f;
if (doDistanceAttenuation) {
if (originalFormula) {
const float DISTANCE_SCALE = 2.5f;
const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f;
const float DISTANCE_LOG_BASE = 2.5f;
const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE);
float distanceSquareToSource = distance * distance;
// calculate the distance coefficient using the distance to this node
distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR,
DISTANCE_SCALE_LOG +
(0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1);
distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());
} else {
// From Fred: If we wanted something that would produce a tail that could go up to 5 seconds in a
// really big room, that would suggest the sound still has to be in the audible after traveling about
// 1500 meters. If its a sound of average volume, we probably have about 30 db, or 5 base2 orders
// of magnitude we can drop down before the sound becomes inaudible. (Thats approximate headroom
// based on a few sloppy assumptions.) So we could try a factor like 1 / (2^(D/300)) for starters.
// 1 / (2^(D/300))
const float DISTANCE_BASE = 2.0f;
const float DISTANCE_DENOMINATOR = 300.0f;
const float DISTANCE_NUMERATOR = 300.0f;
distanceCoefficient = DISTANCE_NUMERATOR / powf(DISTANCE_BASE, (distance / DISTANCE_DENOMINATOR ));
distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());
}
}
return distanceCoefficient;
}
glm::vec3 AudioReflector::getFaceNormal(BoxFace face) {
bool wantSlightRandomness = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSlightlyRandomSurfaces);
glm::vec3 faceNormal;
const float MIN_RANDOM_LENGTH = 0.99f;
const float MAX_RANDOM_LENGTH = 1.0f;
const float NON_RANDOM_LENGTH = 1.0f;
float normalLength = wantSlightRandomness ? randFloatInRange(MIN_RANDOM_LENGTH, MAX_RANDOM_LENGTH) : NON_RANDOM_LENGTH;
float remainder = (1.0f - normalLength)/2.0f;
float remainderSignA = randomSign();
float remainderSignB = randomSign();
if (face == MIN_X_FACE) {
faceNormal = glm::vec3(-normalLength, remainder * remainderSignA, remainder * remainderSignB);
} else if (face == MAX_X_FACE) {
faceNormal = glm::vec3(normalLength, remainder * remainderSignA, remainder * remainderSignB);
} else if (face == MIN_Y_FACE) {
faceNormal = glm::vec3(remainder * remainderSignA, -normalLength, remainder * remainderSignB);
} else if (face == MAX_Y_FACE) {
faceNormal = glm::vec3(remainder * remainderSignA, normalLength, remainder * remainderSignB);
} else if (face == MIN_Z_FACE) {
faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, -normalLength);
} else if (face == MAX_Z_FACE) {
faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, normalLength);
}
return faceNormal;
}
// set up our buffers for our attenuated and delayed samples
const int NUMBER_OF_CHANNELS = 2;
void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint,
const QByteArray& samples, unsigned int sampleTime, int sampleRate) {
bool wantEarSeparation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSeparateEars);
bool wantStereo = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingStereoSource);
glm::vec3 rightEarPosition = wantEarSeparation ? _myAvatar->getHead()->getRightEarPosition() :
_myAvatar->getHead()->getPosition();
glm::vec3 leftEarPosition = wantEarSeparation ? _myAvatar->getHead()->getLeftEarPosition() :
_myAvatar->getHead()->getPosition();
int totalNumberOfSamples = samples.size() / sizeof(int16_t);
int totalNumberOfStereoSamples = samples.size() / (sizeof(int16_t) * NUMBER_OF_CHANNELS);
const int16_t* originalSamplesData = (const int16_t*)samples.constData();
QByteArray attenuatedLeftSamples;
QByteArray attenuatedRightSamples;
attenuatedLeftSamples.resize(samples.size());
attenuatedRightSamples.resize(samples.size());
int16_t* attenuatedLeftSamplesData = (int16_t*)attenuatedLeftSamples.data();
int16_t* attenuatedRightSamplesData = (int16_t*)attenuatedRightSamples.data();
// calculate the distance to the ears
float rightEarDistance = glm::distance(audiblePoint.location, rightEarPosition);
float leftEarDistance = glm::distance(audiblePoint.location, leftEarPosition);
float rightEarDelayMsecs = getDelayFromDistance(rightEarDistance) + audiblePoint.delay;
float leftEarDelayMsecs = getDelayFromDistance(leftEarDistance) + audiblePoint.delay;
float averageEarDelayMsecs = (leftEarDelayMsecs + rightEarDelayMsecs) / 2.0f;
bool safeToInject = true; // assume the best
// check to see if this new injection point would be within the comb filter
// suppression window for any of the existing known delays
QMap<float, float>& knownDelays = (source == INBOUND_AUDIO) ? _inboundAudioDelays : _localAudioDelays;
QMap<float, float>::const_iterator lowerBound = knownDelays.lowerBound(averageEarDelayMsecs - _combFilterWindow);
if (lowerBound != knownDelays.end()) {
float closestFound = lowerBound.value();
float deltaToClosest = (averageEarDelayMsecs - closestFound);
if (deltaToClosest > -_combFilterWindow && deltaToClosest < _combFilterWindow) {
safeToInject = false;
}
}
// keep track of any of our suppressed echoes so we can report them in our statistics
if (!safeToInject) {
QVector<float>& suppressedEchoes = (source == INBOUND_AUDIO) ? _inboundEchoesSuppressed : _localEchoesSuppressed;
suppressedEchoes << averageEarDelayMsecs;
} else {
knownDelays[averageEarDelayMsecs] = averageEarDelayMsecs;
_totalDelay += rightEarDelayMsecs + leftEarDelayMsecs;
_delayCount += 2;
_maxDelay = std::max(_maxDelay,rightEarDelayMsecs);
_maxDelay = std::max(_maxDelay,leftEarDelayMsecs);
_minDelay = std::min(_minDelay,rightEarDelayMsecs);
_minDelay = std::min(_minDelay,leftEarDelayMsecs);
int rightEarDelay = rightEarDelayMsecs * sampleRate / MSECS_PER_SECOND;
int leftEarDelay = leftEarDelayMsecs * sampleRate / MSECS_PER_SECOND;
float rightEarAttenuation = audiblePoint.attenuation *
getDistanceAttenuationCoefficient(rightEarDistance + audiblePoint.distance);
float leftEarAttenuation = audiblePoint.attenuation *
getDistanceAttenuationCoefficient(leftEarDistance + audiblePoint.distance);
_totalAttenuation += rightEarAttenuation + leftEarAttenuation;
_attenuationCount += 2;
_maxAttenuation = std::max(_maxAttenuation,rightEarAttenuation);
_maxAttenuation = std::max(_maxAttenuation,leftEarAttenuation);
_minAttenuation = std::min(_minAttenuation,rightEarAttenuation);
_minAttenuation = std::min(_minAttenuation,leftEarAttenuation);
// run through the samples, and attenuate them
for (int sample = 0; sample < totalNumberOfStereoSamples; sample++) {
int16_t leftSample = originalSamplesData[sample * NUMBER_OF_CHANNELS];
int16_t rightSample = leftSample;
if (wantStereo) {
rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1];
}
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] =
leftSample * leftEarAttenuation * _allEchoesAttenuation;
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0;
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0;
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] =
rightSample * rightEarAttenuation * _allEchoesAttenuation;
}
// now inject the attenuated array with the appropriate delay
unsigned int sampleTimeLeft = sampleTime + leftEarDelay;
unsigned int sampleTimeRight = sampleTime + rightEarDelay;
_audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples);
_audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples);
_injectedEchoes++;
}
}
void AudioReflector::preProcessOriginalInboundAudio(unsigned int sampleTime,
QByteArray& samples, const QAudioFormat& format) {
if (_originalSourceAttenuation != 1.0f) {
int numberOfSamples = (samples.size() / sizeof(int16_t));
int16_t* sampleData = (int16_t*)samples.data();
for (int i = 0; i < numberOfSamples; i++) {
sampleData[i] = sampleData[i] * _originalSourceAttenuation;
}
}
}
void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {
const int NUM_CHANNELS_INPUT = 1;
const int NUM_CHANNELS_OUTPUT = 2;
const int EXPECTED_SAMPLE_RATE = 24000;
if (format.channelCount() == NUM_CHANNELS_INPUT && format.sampleRate() == EXPECTED_SAMPLE_RATE) {
QAudioFormat outputFormat = format;
outputFormat.setChannelCount(NUM_CHANNELS_OUTPUT);
QByteArray stereoInputData(samples.size() * NUM_CHANNELS_OUTPUT, 0);
int numberOfSamples = (samples.size() / sizeof(int16_t));
int16_t* monoSamples = (int16_t*)samples.data();
int16_t* stereoSamples = (int16_t*)stereoInputData.data();
for (int i = 0; i < numberOfSamples; i++) {
stereoSamples[i* NUM_CHANNELS_OUTPUT] = monoSamples[i] * _localAudioAttenuationFactor;
stereoSamples[(i * NUM_CHANNELS_OUTPUT) + 1] = monoSamples[i] * _localAudioAttenuationFactor;
}
_localAudioDelays.clear();
_localEchoesSuppressed.clear();
echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat);
_localEchoesCount = _localAudioDelays.size();
_localEchoesSuppressedCount = _localEchoesSuppressed.size();
}
}
}
void AudioReflector::processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
_inboundAudioDelays.clear();
_inboundEchoesSuppressed.clear();
echoAudio(INBOUND_AUDIO, sampleTime, samples, format);
_inboundEchoesCount = _inboundAudioDelays.size();
_inboundEchoesSuppressedCount = _inboundEchoesSuppressed.size();
}
void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
QMutexLocker locker(&_mutex);
_maxDelay = 0;
_maxAttenuation = 0.0f;
_minDelay = std::numeric_limits<int>::max();
_minAttenuation = std::numeric_limits<float>::max();
_totalDelay = 0.0f;
_delayCount = 0;
_totalAttenuation = 0.0f;
_attenuationCount = 0;
// depending on if we're processing local or external audio, pick the correct points vector
QVector<AudiblePoint>& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints;
int injectCalls = 0;
_injectedEchoes = 0;
foreach(const AudiblePoint& audiblePoint, audiblePoints) {
injectCalls++;
injectAudiblePoint(source, audiblePoint, samples, sampleTime, format.sampleRate());
}
/*
qDebug() << "injectCalls=" << injectCalls;
qDebug() << "_injectedEchoes=" << _injectedEchoes;
*/
_averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount;
_averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount;
if (_reflections == 0) {
_minDelay = 0.0f;
_minAttenuation = 0.0f;
}
_officialMaxDelay = _maxDelay;
_officialMinDelay = _minDelay;
_officialMaxAttenuation = _maxAttenuation;
_officialMinAttenuation = _minAttenuation;
_officialAverageDelay = _averageDelay;
_officialAverageAttenuation = _averageAttenuation;
}
void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) {
glDisable(GL_LIGHTING);
glLineWidth(2.0);
// Draw the vector itself
glBegin(GL_LINES);
glColor3f(color.x,color.y,color.z);
glVertex3f(start.x, start.y, start.z);
glVertex3f(end.x, end.y, end.z);
glEnd();
glEnable(GL_LIGHTING);
}
AudioPath::AudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& direction,
float attenuation, float delay, float distance,bool isDiffusion, int bounceCount) :
source(source),
isDiffusion(isDiffusion),
startPoint(origin),
startDirection(direction),
startDelay(delay),
startAttenuation(attenuation),
lastPoint(origin),
lastDirection(direction),
lastDistance(distance),
lastDelay(delay),
lastAttenuation(attenuation),
bounceCount(bounceCount),
finalized(false),
reflections()
{
}
void AudioReflector::addAudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& initialDirection,
float initialAttenuation, float initialDelay, float initialDistance, bool isDiffusion) {
AudioPath* path = new AudioPath(source, origin, initialDirection, initialAttenuation, initialDelay,
initialDistance, isDiffusion, 0);
QVector<AudioPath*>& audioPaths = source == INBOUND_AUDIO ? _inboundAudioPaths : _localAudioPaths;
audioPaths.push_back(path);
}
// NOTE: This is a prototype of an eventual utility that will identify the speaking sources for the inbound audio
// stream. It's not currently called but will be added soon.
void AudioReflector::identifyAudioSources() {
// looking for audio sources....
foreach (const AvatarSharedPointer& avatarPointer, _avatarManager->getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (!avatar->isInitialized()) {
continue;
}
qDebug() << "avatar["<< avatar <<"] loudness:" << avatar->getAudioLoudness();
}
}
void AudioReflector::calculateAllReflections() {
// only recalculate when we've moved, or if the attributes have changed
// TODO: what about case where new voxels are added in front of us???
bool wantHeadOrientation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingHeadOriented);
glm::quat orientation = wantHeadOrientation ? _myAvatar->getHead()->getFinalOrientationInWorldFrame() : _myAvatar->getOrientation();
glm::vec3 origin = _myAvatar->getHead()->getPosition();
glm::vec3 listenerPosition = _myAvatar->getHead()->getPosition();
bool shouldRecalc = _reflections == 0
|| !isSimilarPosition(origin, _origin)
|| !isSimilarOrientation(orientation, _orientation)
|| !isSimilarPosition(listenerPosition, _listenerPosition)
|| haveAttributesChanged();
if (shouldRecalc) {
QMutexLocker locker(&_mutex);
quint64 start = usecTimestampNow();
_origin = origin;
_orientation = orientation;
_listenerPosition = listenerPosition;
analyzePaths(); // actually does the work
quint64 end = usecTimestampNow();
const bool wantDebugging = false;
if (wantDebugging) {
qDebug() << "newCalculateAllReflections() elapsed=" << (end - start);
}
}
}
void AudioReflector::drawRays() {
const glm::vec3 RED(1,0,0);
const glm::vec3 GREEN(0,1,0);
const glm::vec3 BLUE(0,0,1);
const glm::vec3 CYAN(0,1,1);
int diffusionNumber = 0;
QMutexLocker locker(&_mutex);
// draw the paths for inbound audio
foreach(AudioPath* const& path, _inboundAudioPaths) {
// if this is an original reflection, draw it in RED
if (path->isDiffusion) {
diffusionNumber++;
drawPath(path, GREEN);
} else {
drawPath(path, RED);
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {
// draw the paths for local audio
foreach(AudioPath* const& path, _localAudioPaths) {
// if this is an original reflection, draw it in RED
if (path->isDiffusion) {
diffusionNumber++;
drawPath(path, CYAN);
} else {
drawPath(path, BLUE);
}
}
}
}
void AudioReflector::drawPath(AudioPath* path, const glm::vec3& originalColor) {
glm::vec3 start = path->startPoint;
glm::vec3 color = originalColor;
const float COLOR_ADJUST_PER_BOUNCE = 0.75f;
foreach (glm::vec3 end, path->reflections) {
drawVector(start, end, color);
start = end;
color = color * COLOR_ADJUST_PER_BOUNCE;
}
}
void AudioReflector::clearPaths() {
// clear our inbound audio paths
foreach(AudioPath* const& path, _inboundAudioPaths) {
delete path;
}
_inboundAudioPaths.clear();
_inboundAudiblePoints.clear(); // clear our inbound audible points
// clear our local audio paths
foreach(AudioPath* const& path, _localAudioPaths) {
delete path;
}
_localAudioPaths.clear();
_localAudiblePoints.clear(); // clear our local audible points
}
// Here's how this works: we have an array of AudioPaths, we loop on all of our currently calculating audio
// paths, and calculate one ray per path. If that ray doesn't reflect, or reaches a max distance/attenuation, then it
// is considered finalized.
// If the ray hits a surface, then, based on the characteristics of that surface, it will calculate the new
// attenuation, path length, and delay for the primary path. For surfaces that have diffusion, it will also create
// fanout number of new paths, those new paths will have an origin of the reflection point, and an initial attenuation
// of their diffusion ratio. Those new paths will be added to the active audio paths, and be analyzed for the next loop.
void AudioReflector::analyzePaths() {
clearPaths();
// add our initial paths
glm::vec3 right = glm::normalize(_orientation * IDENTITY_RIGHT);
glm::vec3 up = glm::normalize(_orientation * IDENTITY_UP);
glm::vec3 front = glm::normalize(_orientation * IDENTITY_FRONT);
glm::vec3 left = -right;
glm::vec3 down = -up;
glm::vec3 back = -front;
glm::vec3 frontRightUp = glm::normalize(front + right + up);
glm::vec3 frontLeftUp = glm::normalize(front + left + up);
glm::vec3 backRightUp = glm::normalize(back + right + up);
glm::vec3 backLeftUp = glm::normalize(back + left + up);
glm::vec3 frontRightDown = glm::normalize(front + right + down);
glm::vec3 frontLeftDown = glm::normalize(front + left + down);
glm::vec3 backRightDown = glm::normalize(back + right + down);
glm::vec3 backLeftDown = glm::normalize(back + left + down);
float initialAttenuation = 1.0f;
float preDelay = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay) ? _preDelay : 0.0f;
// NOTE: we're still calculating our initial paths based on the listeners position. But the analysis code has been
// updated to support individual sound sources (which is how we support diffusion), we can use this new paradigm to
// add support for individual sound sources, and more directional sound sources
addAudioPath(INBOUND_AUDIO, _origin, front, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, right, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, up, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, down, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, back, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, left, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, backRightUp, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, backLeftUp, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, backRightDown, initialAttenuation, preDelay);
addAudioPath(INBOUND_AUDIO, _origin, backLeftDown, initialAttenuation, preDelay);
// the original paths for the local audio are directional to the front of the origin
addAudioPath(LOCAL_AUDIO, _origin, front, initialAttenuation, preDelay);
addAudioPath(LOCAL_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay);
addAudioPath(LOCAL_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay);
addAudioPath(LOCAL_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay);
addAudioPath(LOCAL_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay);
// loop through all our audio paths and keep analyzing them until they complete
int steps = 0;
int acitvePaths = _inboundAudioPaths.size() + _localAudioPaths.size(); // when we start, all paths are active
while(acitvePaths > 0) {
acitvePaths = analyzePathsSingleStep();
steps++;
}
_reflections = _inboundAudiblePoints.size() + _localAudiblePoints.size();
_diffusionPathCount = countDiffusionPaths();
}
int AudioReflector::countDiffusionPaths() {
int diffusionCount = 0;
foreach(AudioPath* const& path, _inboundAudioPaths) {
if (path->isDiffusion) {
diffusionCount++;
}
}
foreach(AudioPath* const& path, _localAudioPaths) {
if (path->isDiffusion) {
diffusionCount++;
}
}
return diffusionCount;
}
int AudioReflector::analyzePathsSingleStep() {
// iterate all the active sound paths, calculate one step per active path
int activePaths = 0;
QVector<AudioPath*>* pathsLists[] = { &_inboundAudioPaths, &_localAudioPaths };
for(unsigned int i = 0; i < sizeof(pathsLists) / sizeof(pathsLists[0]); i++) {
QVector<AudioPath*>& pathList = *pathsLists[i];
foreach(AudioPath* const& path, pathList) {
glm::vec3 start = path->lastPoint;
glm::vec3 direction = path->lastDirection;
OctreeElement* elementHit; // output from findRayIntersection
float distance; // output from findRayIntersection
BoxFace face; // output from findRayIntersection
if (!path->finalized) {
activePaths++;
if (path->bounceCount > ABSOLUTE_MAXIMUM_BOUNCE_COUNT) {
path->finalized = true;
} else if (_voxels->findRayIntersection(start, direction, elementHit, distance, face)) {
// TODO: we need to decide how we want to handle locking on the ray intersection, if we force lock,
// we get an accurate picture, but it could prevent rendering of the voxels. If we trylock (default),
// we might not get ray intersections where they may exist, but we can't really detect that case...
// add last parameter of Octree::Lock to force locking
handlePathPoint(path, distance, elementHit, face);
} else {
// If we didn't intersect, but this was a diffusion ray, then we will go ahead and cast a short ray out
// from our last known point, in the last known direction, and leave that sound source hanging there
if (path->isDiffusion) {
const float MINIMUM_RANDOM_DISTANCE = 0.25f;
const float MAXIMUM_RANDOM_DISTANCE = 0.5f;
float distance = randFloatInRange(MINIMUM_RANDOM_DISTANCE, MAXIMUM_RANDOM_DISTANCE);
handlePathPoint(path, distance, NULL, UNKNOWN_FACE);
} else {
path->finalized = true; // if it doesn't intersect, then it is finished
}
}
}
}
}
return activePaths;
}
void AudioReflector::handlePathPoint(AudioPath* path, float distance, OctreeElement* elementHit, BoxFace face) {
glm::vec3 start = path->lastPoint;
glm::vec3 direction = path->lastDirection;
glm::vec3 end = start + (direction * (distance * SLIGHTLY_SHORT));
float currentReflectiveAttenuation = path->lastAttenuation; // only the reflective components
float currentDelay = path->lastDelay; // start with our delay so far
float pathDistance = path->lastDistance;
pathDistance += glm::distance(start, end);
float toListenerDistance = glm::distance(end, _listenerPosition);
// adjust our current delay by just the delay from the most recent ray
currentDelay += getDelayFromDistance(distance);
// now we know the current attenuation for the "perfect" reflection case, but we now incorporate
// our surface materials to determine how much of this ray is absorbed, reflected, and diffused
SurfaceCharacteristics material = getSurfaceCharacteristics(elementHit);
float reflectiveAttenuation = currentReflectiveAttenuation * material.reflectiveRatio;
float totalDiffusionAttenuation = currentReflectiveAttenuation * material.diffusionRatio;
bool wantDiffusions = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions);
int fanout = wantDiffusions ? _diffusionFanout : 0;
float partialDiffusionAttenuation = fanout < 1 ? 0.0f : totalDiffusionAttenuation / (float)fanout;
// total delay includes the bounce back to listener
float totalDelay = currentDelay + getDelayFromDistance(toListenerDistance);
float toListenerAttenuation = getDistanceAttenuationCoefficient(toListenerDistance + pathDistance);
// if our resulting partial diffusion attenuation, is still above our minimum attenuation
// then we add new paths for each diffusion point
if ((partialDiffusionAttenuation * toListenerAttenuation) > MINIMUM_ATTENUATION_TO_REFLECT
&& totalDelay < MAXIMUM_DELAY_MS) {
// diffusions fan out from random places on the semisphere of the collision point
for(int i = 0; i < fanout; i++) {
glm::vec3 diffusion;
// We're creating a random normal here. But we want it to be relatively dramatic compared to how we handle
// our slightly random surface normals.
const float MINIMUM_RANDOM_LENGTH = 0.5f;
const float MAXIMUM_RANDOM_LENGTH = 1.0f;
float randomness = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH);
float remainder = (1.0f - randomness)/2.0f;
float remainderSignA = randomSign();
float remainderSignB = randomSign();
if (face == MIN_X_FACE) {
diffusion = glm::vec3(-randomness, remainder * remainderSignA, remainder * remainderSignB);
} else if (face == MAX_X_FACE) {
diffusion = glm::vec3(randomness, remainder * remainderSignA, remainder * remainderSignB);
} else if (face == MIN_Y_FACE) {
diffusion = glm::vec3(remainder * remainderSignA, -randomness, remainder * remainderSignB);
} else if (face == MAX_Y_FACE) {
diffusion = glm::vec3(remainder * remainderSignA, randomness, remainder * remainderSignB);
} else if (face == MIN_Z_FACE) {
diffusion = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, -randomness);
} else if (face == MAX_Z_FACE) {
diffusion = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, randomness);
} else if (face == UNKNOWN_FACE) {
float randomnessX = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH);
float randomnessY = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH);
float randomnessZ = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH);
diffusion = glm::vec3(direction.x * randomnessX, direction.y * randomnessY, direction.z * randomnessZ);
}
diffusion = glm::normalize(diffusion);
// add new audio path for these diffusions, the new path's source is the same as the original source
addAudioPath(path->source, end, diffusion, partialDiffusionAttenuation, currentDelay, pathDistance, true);
}
} else {
const bool wantDebugging = false;
if (wantDebugging) {
if ((partialDiffusionAttenuation * toListenerAttenuation) <= MINIMUM_ATTENUATION_TO_REFLECT) {
qDebug() << "too quiet to diffuse";
qDebug() << " partialDiffusionAttenuation=" << partialDiffusionAttenuation;
qDebug() << " toListenerAttenuation=" << toListenerAttenuation;
qDebug() << " result=" << (partialDiffusionAttenuation * toListenerAttenuation);
qDebug() << " MINIMUM_ATTENUATION_TO_REFLECT=" << MINIMUM_ATTENUATION_TO_REFLECT;
}
if (totalDelay > MAXIMUM_DELAY_MS) {
qDebug() << "too delayed to diffuse";
qDebug() << " totalDelay=" << totalDelay;
qDebug() << " MAXIMUM_DELAY_MS=" << MAXIMUM_DELAY_MS;
}
}
}
// if our reflective attenuation is above our minimum, then add our reflection point and
// allow our path to continue
if (((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation) > MINIMUM_ATTENUATION_TO_REFLECT
&& totalDelay < MAXIMUM_DELAY_MS) {
// add this location, as the reflective attenuation as well as the total diffusion attenuation
// NOTE: we add the delay to the audible point, not back to the listener. The additional delay
// and attenuation to the listener is recalculated at the point where we actually inject the
// audio so that it can be adjusted to ear position
AudiblePoint point = {end, currentDelay, (reflectiveAttenuation + totalDiffusionAttenuation), pathDistance};
QVector<AudiblePoint>& audiblePoints = path->source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints;
audiblePoints.push_back(point);
// add this location to the path points, so we can visualize it
path->reflections.push_back(end);
// now, if our reflective attenuation is over our minimum then keep going...
if (reflectiveAttenuation * toListenerAttenuation > MINIMUM_ATTENUATION_TO_REFLECT) {
glm::vec3 faceNormal = getFaceNormal(face);
path->lastDirection = glm::normalize(glm::reflect(direction,faceNormal));
path->lastPoint = end;
path->lastAttenuation = reflectiveAttenuation;
path->lastDelay = currentDelay;
path->lastDistance = pathDistance;
path->bounceCount++;
} else {
path->finalized = true; // if we're too quiet, then we're done
}
} else {
const bool wantDebugging = false;
if (wantDebugging) {
if (((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation) <= MINIMUM_ATTENUATION_TO_REFLECT) {
qDebug() << "too quiet to add audible point";
qDebug() << " reflectiveAttenuation + totalDiffusionAttenuation=" << (reflectiveAttenuation + totalDiffusionAttenuation);
qDebug() << " toListenerAttenuation=" << toListenerAttenuation;
qDebug() << " result=" << ((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation);
qDebug() << " MINIMUM_ATTENUATION_TO_REFLECT=" << MINIMUM_ATTENUATION_TO_REFLECT;
}
if (totalDelay > MAXIMUM_DELAY_MS) {
qDebug() << "too delayed to add audible point";
qDebug() << " totalDelay=" << totalDelay;
qDebug() << " MAXIMUM_DELAY_MS=" << MAXIMUM_DELAY_MS;
}
}
path->finalized = true; // if we're too quiet, then we're done
}
}
// TODO: eventually we will add support for different surface characteristics based on the element
// that is hit, which is why we pass in the elementHit to this helper function. But for now, all
// surfaces have the same characteristics
SurfaceCharacteristics AudioReflector::getSurfaceCharacteristics(OctreeElement* elementHit) {
SurfaceCharacteristics result = { getReflectiveRatio(), _absorptionRatio, _diffusionRatio };
return result;
}
void AudioReflector::setReflectiveRatio(float ratio) {
float safeRatio = std::max(0.0f, std::min(ratio, 1.0f));
float currentReflectiveRatio = (1.0f - (_absorptionRatio + _diffusionRatio));
float halfDifference = (safeRatio - currentReflectiveRatio) / 2.0f;
// evenly distribute the difference between the two other ratios
_absorptionRatio -= halfDifference;
_diffusionRatio -= halfDifference;
}
void AudioReflector::setAbsorptionRatio(float ratio) {
float safeRatio = std::max(0.0f, std::min(ratio, 1.0f));
_absorptionRatio = safeRatio;
const float MAX_COMBINED_RATIO = 1.0f;
if (_absorptionRatio + _diffusionRatio > MAX_COMBINED_RATIO) {
_diffusionRatio = MAX_COMBINED_RATIO - _absorptionRatio;
}
}
void AudioReflector::setDiffusionRatio(float ratio) {
float safeRatio = std::max(0.0f, std::min(ratio, 1.0f));
_diffusionRatio = safeRatio;
const float MAX_COMBINED_RATIO = 1.0f;
if (_absorptionRatio + _diffusionRatio > MAX_COMBINED_RATIO) {
_absorptionRatio = MAX_COMBINED_RATIO - _diffusionRatio;
}
}

View file

@ -1,254 +0,0 @@
//
// AudioReflector.h
// interface
//
// Created by Brad Hefta-Gaub on 4/2/2014
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef interface_AudioReflector_h
#define interface_AudioReflector_h
#include <QMutex>
#include <VoxelTree.h>
#include "Audio.h"
#include "avatar/MyAvatar.h"
#include "avatar/AvatarManager.h"
enum AudioSource {
LOCAL_AUDIO,
INBOUND_AUDIO
};
class AudioPath {
public:
AudioPath(AudioSource source = INBOUND_AUDIO, const glm::vec3& origin = glm::vec3(0.0f),
const glm::vec3& direction = glm::vec3(0.0f), float attenuation = 1.0f,
float delay = 0.0f, float distance = 0.0f, bool isDiffusion = false, int bounceCount = 0);
AudioSource source;
bool isDiffusion;
glm::vec3 startPoint;
glm::vec3 startDirection;
float startDelay;
float startAttenuation;
glm::vec3 lastPoint;
glm::vec3 lastDirection;
float lastDistance;
float lastDelay;
float lastAttenuation;
unsigned int bounceCount;
bool finalized;
QVector<glm::vec3> reflections;
};
class AudiblePoint {
public:
glm::vec3 location; /// location of the audible point
float delay; /// includes total delay including pre delay to the point of the audible location, not to the listener's ears
float attenuation; /// only the reflective & diffusive portion of attenuation, doesn't include distance attenuation
float distance; /// includes total distance to the point of the audible location, not to the listener's ears
};
class SurfaceCharacteristics {
public:
float reflectiveRatio;
float absorptionRatio;
float diffusionRatio;
};
class AudioReflector : public QObject {
Q_OBJECT
public:
AudioReflector(QObject* parent = NULL);
// setup functions to configure the resources used by the AudioReflector
void setVoxels(VoxelTree* voxels) { _voxels = voxels; }
void setMyAvatar(MyAvatar* myAvatar) { _myAvatar = myAvatar; }
void setAudio(Audio* audio) { _audio = audio; }
void setAvatarManager(AvatarManager* avatarManager) { _avatarManager = avatarManager; }
void render(); /// must be called in the application render loop
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
public slots:
// statistics
int getReflections() const { return _reflections; }
float getAverageDelayMsecs() const { return _officialAverageDelay; }
float getAverageAttenuation() const { return _officialAverageAttenuation; }
float getMaxDelayMsecs() const { return _officialMaxDelay; }
float getMaxAttenuation() const { return _officialMaxAttenuation; }
float getMinDelayMsecs() const { return _officialMinDelay; }
float getMinAttenuation() const { return _officialMinAttenuation; }
float getDelayFromDistance(float distance);
int getDiffusionPathCount() const { return _diffusionPathCount; }
int getEchoesInjected() const { return _inboundEchoesCount + _localEchoesCount; }
int getEchoesSuppressed() const { return _inboundEchoesSuppressedCount + _localEchoesSuppressedCount; }
/// ms of delay added to all echos
float getPreDelay() const { return _preDelay; }
void setPreDelay(float preDelay) { _preDelay = preDelay; }
/// ms per meter that sound travels, larger means slower, which sounds bigger
float getSoundMsPerMeter() const { return _soundMsPerMeter; }
void setSoundMsPerMeter(float soundMsPerMeter) { _soundMsPerMeter = soundMsPerMeter; }
/// scales attenuation to be louder or softer than the default distance attenuation
float getDistanceAttenuationScalingFactor() const { return _distanceAttenuationScalingFactor; }
void setDistanceAttenuationScalingFactor(float factor) { _distanceAttenuationScalingFactor = factor; }
/// scales attenuation of local audio to be louder or softer than the default attenuation
float getLocalAudioAttenuationFactor() const { return _localAudioAttenuationFactor; }
void setLocalAudioAttenuationFactor(float factor) { _localAudioAttenuationFactor = factor; }
/// ms window in which we will suppress echoes to reduce comb filter effects
float getCombFilterWindow() const { return _combFilterWindow; }
void setCombFilterWindow(float value) { _combFilterWindow = value; }
/// number of points of diffusion from each reflection point, as fanout increases there are more chances for secondary
/// echoes, but each diffusion ray is quieter and therefore more likely to be below the sound floor
int getDiffusionFanout() const { return _diffusionFanout; }
void setDiffusionFanout(int fanout) { _diffusionFanout = fanout; }
/// ratio 0.0 - 1.0 of amount of each ray that is absorbed upon hitting a surface
float getAbsorptionRatio() const { return _absorptionRatio; }
void setAbsorptionRatio(float ratio);
// ratio 0.0 - 1.0 of amount of each ray that is diffused upon hitting a surface
float getDiffusionRatio() const { return _diffusionRatio; }
void setDiffusionRatio(float ratio);
// remaining ratio 0.0 - 1.0 of amount of each ray that is cleanly reflected upon hitting a surface
float getReflectiveRatio() const { return (1.0f - (_absorptionRatio + _diffusionRatio)); }
void setReflectiveRatio(float ratio);
// wet/dry mix - these don't affect any reflection calculations, only the final mix volumes
float getOriginalSourceAttenuation() const { return _originalSourceAttenuation; }
void setOriginalSourceAttenuation(float value) { _originalSourceAttenuation = value; }
float getEchoesAttenuation() const { return _allEchoesAttenuation; }
void setEchoesAttenuation(float value) { _allEchoesAttenuation = value; }
signals:
private:
VoxelTree* _voxels; // used to access voxel scene
MyAvatar* _myAvatar; // access to listener
Audio* _audio; // access to audio API
AvatarManager* _avatarManager; // access to avatar manager API
// Helpers for drawing
void drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color);
// helper for generically calculating attenuation based on distance
float getDistanceAttenuationCoefficient(float distance);
// statistics
int _reflections;
int _diffusionPathCount;
int _delayCount;
float _totalDelay;
float _averageDelay;
float _maxDelay;
float _minDelay;
float _officialAverageDelay;
float _officialMaxDelay;
float _officialMinDelay;
int _attenuationCount;
float _totalAttenuation;
float _averageAttenuation;
float _maxAttenuation;
float _minAttenuation;
float _officialAverageAttenuation;
float _officialMaxAttenuation;
float _officialMinAttenuation;
glm::vec3 _listenerPosition;
glm::vec3 _origin;
glm::quat _orientation;
QVector<AudioPath*> _inboundAudioPaths; /// audio paths we're processing for inbound audio
QVector<AudiblePoint> _inboundAudiblePoints; /// the audible points that have been calculated from the inbound audio paths
QMap<float, float> _inboundAudioDelays; /// delay times for currently injected audio points
QVector<float> _inboundEchoesSuppressed; /// delay times for currently injected audio points
int _inboundEchoesCount;
int _inboundEchoesSuppressedCount;
QVector<AudioPath*> _localAudioPaths; /// audio paths we're processing for local audio
QVector<AudiblePoint> _localAudiblePoints; /// the audible points that have been calculated from the local audio paths
QMap<float, float> _localAudioDelays; /// delay times for currently injected audio points
QVector<float> _localEchoesSuppressed; /// delay times for currently injected audio points
int _localEchoesCount;
int _localEchoesSuppressedCount;
// adds a sound source to begin an audio path trace, these can be the initial sound sources with their directional properties,
// as well as diffusion sound sources
void addAudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& initialDirection, float initialAttenuation,
float initialDelay, float initialDistance = 0.0f, bool isDiffusion = false);
// helper that handles audioPath analysis
int analyzePathsSingleStep();
void handlePathPoint(AudioPath* path, float distance, OctreeElement* elementHit, BoxFace face);
void clearPaths();
void analyzePaths();
void drawRays();
void drawPath(AudioPath* path, const glm::vec3& originalColor);
void calculateAllReflections();
int countDiffusionPaths();
glm::vec3 getFaceNormal(BoxFace face);
void identifyAudioSources();
void injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, const QByteArray& samples, unsigned int sampleTime, int sampleRate);
void echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
// return the surface characteristics of the element we hit
SurfaceCharacteristics getSurfaceCharacteristics(OctreeElement* elementHit = NULL);
QMutex _mutex;
float _preDelay;
float _soundMsPerMeter;
float _distanceAttenuationScalingFactor;
float _localAudioAttenuationFactor;
float _combFilterWindow;
int _diffusionFanout; // number of points of diffusion from each reflection point
// all elements have the same material for now...
float _absorptionRatio;
float _diffusionRatio;
float _reflectiveRatio;
// wet/dry mix - these don't affect any reflection calculations, only the final mix volumes
float _originalSourceAttenuation; /// each sample of original signal will be multiplied by this
float _allEchoesAttenuation; /// each sample of all echo signals will be multiplied by this
// remember the last known values at calculation
bool haveAttributesChanged();
bool _withDiffusion;
float _lastPreDelay;
float _lastSoundMsPerMeter;
float _lastDistanceAttenuationScalingFactor;
float _lastLocalAudioAttenuationFactor;
int _lastDiffusionFanout;
float _lastAbsorptionRatio;
float _lastDiffusionRatio;
bool _lastDontDistanceAttenuate;
bool _lastAlternateDistanceAttenuate;
int _injectedEchoes;
};
#endif // interface_AudioReflector_h

View file

@ -95,22 +95,17 @@ void Camera::setFarClip(float f) {
}
PickRay Camera::computePickRay(float x, float y) {
float screenWidth = Application::getInstance()->getGLWidget()->width();
float screenHeight = Application::getInstance()->getGLWidget()->height();
PickRay result;
if (OculusManager::isConnected()) {
result.origin = getPosition();
Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction);
} else {
Application::getInstance()->getViewFrustum()->computePickRay(x / screenWidth, y / screenHeight,
result.origin, result.direction);
}
return result;
GLCanvas::SharedPointer glCanvas = DependencyManager::get<GLCanvas>();
return computeViewPickRay(x / glCanvas->width(), y / glCanvas->height());
}
PickRay Camera::computeViewPickRay(float xRatio, float yRatio) {
PickRay result;
Application::getInstance()->getViewFrustum()->computePickRay(xRatio, yRatio, result.origin, result.direction);
if (OculusManager::isConnected()) {
Application::getInstance()->getApplicationOverlay().computeOculusPickRay(xRatio, yRatio, result.origin, result.direction);
} else {
Application::getInstance()->getViewFrustum()->computePickRay(xRatio, yRatio, result.origin, result.direction);
}
return result;
}

View file

@ -17,11 +17,12 @@
#include <GeometryUtil.h>
#include <PacketHeaders.h>
#include <PathUtils.h>
#include <ProgramObject.h>
#include <SharedUtil.h>
#include "Application.h"
#include "Camera.h"
#include "renderer/ProgramObject.h"
#include "world.h"
#include "Environment.h"
@ -188,7 +189,7 @@ int Environment::parseData(const HifiSockAddr& senderAddress, const QByteArray&
ProgramObject* Environment::createSkyProgram(const char* from, int* locations) {
ProgramObject* program = new ProgramObject();
QByteArray prefix = QString(Application::resourcesPath() + "/shaders/SkyFrom" + from).toUtf8();
QByteArray prefix = QString(PathUtils::resourcesPath() + "/shaders/SkyFrom" + from).toUtf8();
program->addShaderFromSourceFile(QGLShader::Vertex, prefix + ".vert");
program->addShaderFromSourceFile(QGLShader::Fragment, prefix + ".frag");
program->link();
@ -261,7 +262,7 @@ void Environment::renderAtmosphere(Camera& camera, const EnvironmentData& data)
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);
Application::getInstance()->getGeometryCache()->renderSphere(1.0f, 100, 50); //Draw a unit sphere
DependencyManager::get<GeometryCache>()->renderSphere(1.0f, 100, 50); //Draw a unit sphere
glDepthMask(GL_TRUE);
program->release();

Some files were not shown because too many files have changed in this diff Show more