diff --git a/cmake/modules/FindLibOVR.cmake b/cmake/modules/FindLibOVR.cmake index cad962732c..56f9ece6d8 100644 --- a/cmake/modules/FindLibOVR.cmake +++ b/cmake/modules/FindLibOVR.cmake @@ -6,31 +6,31 @@ # # LIBOVR_FOUND - system found LibOVR # LIBOVR_INCLUDE_DIRS - the LibOVR include directory -# LIBOVR_LIBRARY - Link this to use LibOVR +# LIBOVR_LIBRARIES - Link this to use LibOVR # # Created on 5/9/2013 by Stephen Birarda # Copyright (c) 2013 High Fidelity # -if (LIBOVR_LIBRARY AND LIBOVR_INCLUDE_DIRS) +if (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS) # in cache already set(LIBOVR_FOUND TRUE) -else (LIBOVR_LIBRARY AND LIBOVR_INCLUDE_DIRS) - set(LIBOVR_INCLUDE_DIRS ${LIBOVR_ROOT_DIR}/Include) +else (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS) + find_path(LIBOVR_INCLUDE_DIRS OVR.h ${LIBOVR_ROOT_DIR}/Include) if (APPLE) - set(LIBOVR_LIBRARY ${LIBOVR_ROOT_DIR}/Lib/MacOS/libovr.a) - else (WIN32) - set(LIBOVR_LIBRARY ${LIBOVR_ROOT_DIR}/Lib/Win32/libovr.lib) + find_library(LIBOVR_LIBRARIES libovr.a ${LIBOVR_ROOT_DIR}/Lib/MacOS/) + elseif (WIN32) + find_library(LIBOVR_LIBRARIES libovr.lib ${LIBOVR_ROOT_DIR}/Lib/Win32/) endif () - if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARY) + if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES) set(LIBOVR_FOUND TRUE) - endif (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARY) + endif (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES) if (LIBOVR_FOUND) if (NOT LibOVR_FIND_QUIETLY) - message(STATUS "Found LibOVR: ${LIBOVR_LIBRARY}") + message(STATUS "Found LibOVR: ${LIBOVR_LIBRARIES}") endif (NOT LibOVR_FIND_QUIETLY) else (LIBOVR_FOUND) if (LibOVR_FIND_REQUIRED) @@ -38,7 +38,7 @@ else (LIBOVR_LIBRARY AND LIBOVR_INCLUDE_DIRS) endif (LibOVR_FIND_REQUIRED) endif (LIBOVR_FOUND) - # show the LIBOVR_INCLUDE_DIRS and LIBOVR_LIBRARY variables only in the advanced view - mark_as_advanced(LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARY) + # show the LIBOVR_INCLUDE_DIRS and LIBOVR_LIBRARIES variables only in the advanced view + mark_as_advanced(LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARIES) -endif (LIBOVR_LIBRARY AND LIBOVR_INCLUDE_DIRS) \ No newline at end of file +endif (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS) \ No newline at end of file diff --git a/cmake/modules/FindLodePNG.cmake b/cmake/modules/FindLodePNG.cmake index 4568d59765..c36e238029 100644 --- a/cmake/modules/FindLodePNG.cmake +++ b/cmake/modules/FindLodePNG.cmake @@ -6,34 +6,26 @@ # # LODEPNG_FOUND - system has LODEPNG_FOUND # LODEPNG_INCLUDE_DIRS - the LodePNG include directory -# LODEPNG_LIBRARY - Link these to use LodePNG +# LODEPNG_LIBRARIES- Link these to use LodePNG # # Copyright (c) 2013 Stephen Birarda # -if (LODEPNG_LIBRARY AND LODEPNG_INCLUDE_DIRS) +if (LODEPNG_LIBRARIES AND LODEPNG_INCLUDE_DIRS) # in cache already set(LODEPNG_FOUND TRUE) -else (LODEPNG_LIBRARY AND LODEPNG_INCLUDE_DIRS) +else (LODEPNG_LIBRARIES AND LODEPNG_INCLUDE_DIRS) - FIND_PATH(LODEPNG_INCLUDE_DIR "lodepng.h" - PATHS ${LODEPNG_ROOT_DIR}) + find_path(LODEPNG_INCLUDE_DIRS lodepng.h ${LODEPNG_ROOT_DIR}) + find_file(LODEPNG_LIBRARIES lodepng.cpp ${LODEPNG_ROOT_DIR}) - set(LODEPNG_INCLUDE_DIRS - ${LODEPNG_INCLUDE_DIR} - ) - - set(LODEPNG_LIBRARY - ${LODEPNG_ROOT_DIR}/lodepng.cpp - ) - - if (LODEPNG_INCLUDE_DIRS AND LODEPNG_LIBRARY) + if (LODEPNG_INCLUDE_DIRS AND LODEPNG_LIBRARIES) set(LODEPNG_FOUND TRUE) - endif (LODEPNG_INCLUDE_DIRS AND LODEPNG_LIBRARY) + endif (LODEPNG_INCLUDE_DIRS AND LODEPNG_LIBRARIES) if (LODEPNG_FOUND) if (NOT LodePNG_FIND_QUIETLY) - message(STATUS "Found LodePNG: ${LODEPNG_LIBRARY}") + message(STATUS "Found LodePNG: ${LODEPNG_LIBRARIES}") endif (NOT LodePNG_FIND_QUIETLY) else (LODEPNG_FOUND) if (LodePNG_FIND_REQUIRED) @@ -41,7 +33,7 @@ else (LODEPNG_LIBRARY AND LODEPNG_INCLUDE_DIRS) endif (LodePNG_FIND_REQUIRED) endif (LODEPNG_FOUND) - # show the LODEPNG_INCLUDE_DIRS and LODEPNG_LIBRARY variables only in the advanced view - mark_as_advanced(LODEPNG_INCLUDE_DIRS LODEPNG_LIBRARY) + # show the LODEPNG_INCLUDE_DIRS and LODEPNG_LIBRARIES variables only in the advanced view + mark_as_advanced(LODEPNG_INCLUDE_DIRS LODEPNG_LIBRARIES) -endif (LODEPNG_LIBRARY AND LODEPNG_INCLUDE_DIRS) \ No newline at end of file +endif (LODEPNG_LIBRARIES AND LODEPNG_INCLUDE_DIRS) \ No newline at end of file diff --git a/cmake/modules/FindPortAudio.cmake b/cmake/modules/FindPortAudio.cmake new file mode 100644 index 0000000000..4a796e16b4 --- /dev/null +++ b/cmake/modules/FindPortAudio.cmake @@ -0,0 +1,44 @@ +# Find the static PortAudio library +# +# You must provide a PORTAUDIO_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# PORTAUDIO_FOUND - system found PortAudio +# PORTAUDIO_INCLUDE_DIRS - the PortAudio include directory +# PORTAUDIO_LIBRARIES - Link this to use PortAudio +# +# Created on 5/14/2013 by Stephen Birarda +# Copyright (c) 2013 High Fidelity +# + +if (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + # in cache already + set(PORTAUDIO_FOUND TRUE) +else (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + find_path(PORTAUDIO_INCLUDE_DIRS portaudio.h ${PORTAUDIO_ROOT_DIR}/include) + + if (APPLE) + find_library(PORTAUDIO_LIBRARIES libportaudio.a ${PORTAUDIO_ROOT_DIR}/lib/MacOS/) + elseif (UNIX) + find_library(PORTAUDIO_LIBRARIES libportaudio.a ${PORTAUDIO_ROOT_DIR}/lib/UNIX/) + endif () + + if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + set(PORTAUDIO_FOUND TRUE) + endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + + if (PORTAUDIO_FOUND) + if (NOT PortAudio_FIND_QUIETLY) + message(STATUS "Found PortAudio: ${PORTAUDIO_LIBRARIES}") + endif (NOT PortAudio_FIND_QUIETLY) + else (PORTAUDIO_FOUND) + if (PortAudio_FIND_REQUIRED) + message(FATAL_ERROR "Could not find PortAudio") + endif (PortAudio_FIND_REQUIRED) + endif (PORTAUDIO_FOUND) + + # show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view + mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) + +endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 156e5d8a7c..e00db98947 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -10,7 +10,7 @@ project(${TARGET_NAME}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") set(LODEPNG_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LodePNG) set(LIBOVR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LibOVR) -set(PORTAUDIO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/portaudio) +set(PORTAUDIO_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/portaudio) if (APPLE) set(GL_HEADERS "#include \n#include ") @@ -55,7 +55,7 @@ if (APPLE) SET(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_RSRCS}) endif (APPLE) -find_package(Qt4 REQUIRED QtCore QtGui) +find_package(Qt4 REQUIRED QtCore QtGui QtOpenGL) include(${QT_USE_FILE}) # run qt moc on qt-enabled headers @@ -109,7 +109,7 @@ if (APPLE) ${GLUT} ${OpenGL} ${IOKit} - ${LIBOVR_LIBRARY} + ${LIBOVR_LIBRARIES} ) else (APPLE) find_package(OpenGL REQUIRED) @@ -129,30 +129,12 @@ if (WIN32) wsock32.lib ) else (WIN32) - target_link_libraries(${TARGET_NAME} ${LODEPNG_LIBRARY}) - - # include PortAudio as external project - include(ExternalProject) - set(PORTAUDIO_PROJ_DIR external/portaudio) - ExternalProject_Add( - portaudio - PREFIX ${PORTAUDIO_PROJ_DIR} - BINARY_DIR ${PORTAUDIO_PROJ_DIR}/src/portaudio - URL ${PORTAUDIO_DIR}/pa_snapshot_020813.tgz - CONFIGURE_COMMAND /configure --prefix "${PROJECT_BINARY_DIR}/${PORTAUDIO_PROJ_DIR}" - BUILD_COMMAND make - ) - - # make PortAudio a dependency of the interface executable - add_dependencies(${TARGET_NAME} portaudio) - - # include the PortAudio headers - ExternalProject_Get_Property(portaudio source_dir) - include_directories(${source_dir}/include) + target_link_libraries(${TARGET_NAME} ${LODEPNG_LIBRARIES}) # link the PortAudio library - ExternalProject_Get_Property(portaudio binary_dir) - target_link_libraries(${TARGET_NAME} ${binary_dir}/lib/.libs/libportaudio.a) + find_package(PortAudio REQUIRED) + include_directories(${PORTAUDIO_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${PORTAUDIO_LIBRARIES}) # link required libraries on UNIX if (UNIX AND NOT APPLE) diff --git a/interface/external/PortAudio/include/portaudio.h b/interface/external/PortAudio/include/portaudio.h new file mode 100644 index 0000000000..5e11dad017 --- /dev/null +++ b/interface/external/PortAudio/include/portaudio.h @@ -0,0 +1,1174 @@ +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id: portaudio.h 1859 2012-09-01 00:10:13Z philburk $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup public_header + @brief The portable PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, + paCanNotWriteToACallbackStream, + paCanNotReadFromAnOutputOnlyStream, + paCanNotWriteToAnInputOnlyStream, + paIncompatibleStreamHostApi, + paBadBufferPtr +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initializes internal data structures and prepares underlying + host APIs for use. With the exception of Pa_GetVersion(), Pa_GetVersionText(), + and Pa_GetErrorText(), this function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initialized by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_GetDeviceCount()-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_GetDeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12, + paWASAPI=13, + paAudioScienceHPI=14 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asynchronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constraining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the default host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds. PaTime is + used for the fields of the PaStreamCallbackTimeInfo argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + PaTime values have unspecified origin. + + @see PaStreamCallback, PaStreamCallbackTimeInfo, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that audio data is passed as an array + of pointers to separate buffers, one buffer for each channel. Usually, + when this flag is not used, audio data is passed as a single buffer with + all channels interleaved. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000) /**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */ + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /** Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /** Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + should round the suggestedLatency up to the next practical value - ie to + provide an equal or higher latency than suggestedLatency wherever possible. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. + + Time values are expressed in seconds and are synchronised with the time base used by Pa_GetStreamTime() for the associated stream. + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; /**< The time when the first sample of the input buffer was captured at the ADC input */ + PaTime currentTime; /**< The time when the stream callback was invoked */ + PaTime outputBufferDacTime; /**< The time when the first sample of the output buffer will output the DAC */ +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, /**< Signal that the stream should continue invoking the callback and processing audio. */ + paComplete=1, /**< Signal that the stream should stop invoking the callback and finish once all output samples have played. */ + paAbort=2 /**< Signal that the stream should stop invoking the callback and finish as soon as possible. */ +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + When a stream is running, PortAudio calls the stream callback periodically. + The callback function is responsible for processing buffers of audio samples + passed via the input and output parameters. + + The PortAudio stream callback runs at very high or real-time priority. + It is required to consistently meet its time deadlines. Do not allocate + memory, access the file system, call library functions or call other functions + from the stream callback that may block or take an unpredictable amount of + time to complete. + + In order for a stream to maintain glitch-free operation the callback + must consume and return audio data faster than it is recorded and/or + played. PortAudio anticipates that each callback invocation may execute for + a duration approaching the duration of frameCount audio frames at the stream + sample rate. It is reasonable to expect to be able to utilise 70% or more of + the available CPU time in the PortAudio callback. However, due to buffer size + adaption and other factors, not all host APIs are able to guarantee audio + stability under heavy CPU load with arbitrary fixed callback buffer sizes. + When high callback CPU utilisation is required the most robust behavior + can be achieved by using paFramesPerBufferUnspecified as the + Pa_OpenStream() framesPerBuffer parameter. + + @param input and @param output are either arrays of interleaved samples or; + if non-interleaved samples were requested using the paNonInterleaved sample + format flag, an array of buffer pointers, one non-interleaved buffer for + each channel. + + The format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo Timestamps indicating the ADC capture time of the first sample + in the input buffer, the DAC output time of the first sample in the output buffer + and the time the callback was invoked. + See PaStreamCallbackTimeInfo and Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + ::PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilize this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissible to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will receive an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behavior of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output device. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter is invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Returns the current time in seconds for a stream according to the same clock used + to generate callback PaStreamCallbackTimeInfo timestamps. The time values are + monotonically increasing and have unspecified origin. + + Pa_GetStreamTime returns valid time values for the entire life of the stream, + from when the stream is opened until it is closed. Starting and stopping the stream + does not affect the passage of time returned by Pa_GetStreamTime. + + This time may be used for synchronizing other events to the audio stream, for + example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback, PaStreamCallbackTimeInfo +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/interface/external/PortAudio/lib/MacOS/libportaudio.a b/interface/external/PortAudio/lib/MacOS/libportaudio.a new file mode 100644 index 0000000000..93cd6b8432 Binary files /dev/null and b/interface/external/PortAudio/lib/MacOS/libportaudio.a differ diff --git a/interface/external/PortAudio/lib/UNIX/libportaudio.a b/interface/external/PortAudio/lib/UNIX/libportaudio.a new file mode 100644 index 0000000000..6803bb32ac Binary files /dev/null and b/interface/external/PortAudio/lib/UNIX/libportaudio.a differ diff --git a/interface/external/portaudio/pa_snapshot_020813.tgz b/interface/external/portaudio/pa_snapshot_020813.tgz deleted file mode 100644 index 1f71d1035c..0000000000 Binary files a/interface/external/portaudio/pa_snapshot_020813.tgz and /dev/null differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1def8e4b2e..7844f08686 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5,20 +5,1986 @@ // Created by Andrzej Kapolka on 5/10/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +#include + +#include + +#ifdef _WIN32 +#include "Syssocket.h" +#include "Systime.h" +#else +#include +#include +#include +#endif + +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include + #include "Application.h" +#include "InterfaceConfig.h" +#include "Log.h" +#include "OculusManager.h" +#include "Util.h" +#include "renderer/ProgramObject.h" +#include "ui/TextRenderer.h" -Application::Application(int& argc, char** argv) : QApplication(argc, argv) { - // simple menu bar (will only appear on OS X, for now) - QMenuBar* menuBar = new QMenuBar(); - QMenu* fileMenu = menuBar->addMenu("File"); +using namespace std; - fileMenu->addAction("Pair", this, SLOT(pair())); +// Starfield information +static char STAR_FILE[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; +static char STAR_CACHE_FILE[] = "cachedStars.txt"; + +const glm::vec3 START_LOCATION(6.1f, 0, 1.4f); // Where one's own agent begins in the world + // (will be overwritten if avatar data file is found) + +const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff + // in the idle loop? (60 FPS is default) + +const bool USING_MOUSE_VIEW_SHIFT = false; +const float MOUSE_VIEW_SHIFT_RATE = 40.0f; +const float MOUSE_VIEW_SHIFT_YAW_MARGIN = (float)(1200 * 0.2f); +const float MOUSE_VIEW_SHIFT_PITCH_MARGIN = (float)(800 * 0.2f); +const float MOUSE_VIEW_SHIFT_YAW_LIMIT = 45.0; +const float MOUSE_VIEW_SHIFT_PITCH_LIMIT = 30.0; + +const bool DISPLAY_HEAD_MOUSE = true; + +// customized canvas that simply forwards requests/events to the singleton application +class GLCanvas : public QGLWidget { +protected: + + virtual void initializeGL(); + virtual void paintGL(); + virtual void resizeGL(int width, int height); + + virtual void keyPressEvent(QKeyEvent* event); + virtual void keyReleaseEvent(QKeyEvent* event); + + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + + virtual void wheelEvent(QWheelEvent* event); +}; + +void GLCanvas::initializeGL() { + static_cast(QCoreApplication::instance())->initializeGL(); +} + +void GLCanvas::paintGL() { + static_cast(QCoreApplication::instance())->paintGL(); +} + +void GLCanvas::resizeGL(int width, int height) { + static_cast(QCoreApplication::instance())->resizeGL(width, height); +} + +void GLCanvas::keyPressEvent(QKeyEvent* event) { + static_cast(QCoreApplication::instance())->keyPressEvent(event); +} + +void GLCanvas::keyReleaseEvent(QKeyEvent* event) { + static_cast(QCoreApplication::instance())->keyReleaseEvent(event); +} + +void GLCanvas::mouseMoveEvent(QMouseEvent* event) { + static_cast(QCoreApplication::instance())->mouseMoveEvent(event); +} + +void GLCanvas::mousePressEvent(QMouseEvent* event) { + static_cast(QCoreApplication::instance())->mousePressEvent(event); +} + +void GLCanvas::mouseReleaseEvent(QMouseEvent* event) { + static_cast(QCoreApplication::instance())->mouseReleaseEvent(event); +} + +void GLCanvas::wheelEvent(QWheelEvent* event) { + static_cast(QCoreApplication::instance())->wheelEvent(event); +} + +Application::Application(int& argc, char** argv) : + QApplication(argc, argv), + _window(new QMainWindow(desktop())), + _glWidget(new GLCanvas()), + _displayLevels(false), + _frameCount(0), + _fps(120.0f), + _justStarted(true), + _wantToKillLocalVoxels(false), + _frustumDrawingMode(FRUSTUM_DRAW_MODE_ALL), + _viewFrustumOffsetYaw(-135.0), + _viewFrustumOffsetPitch(0.0), + _viewFrustumOffsetRoll(0.0), + _viewFrustumOffsetDistance(25.0), + _viewFrustumOffsetUp(0.0), + _mouseViewShiftYaw(0.0f), + _mouseViewShiftPitch(0.0f), + _audioScope(256, 200, true), + _myAvatar(true), + _mouseX(0), + _mouseY(0), + _mousePressed(false), + _mouseMode(NO_EDIT_MODE), + _mouseVoxelScale(1.0f / 1024.0f), + _paintOn(false), + _dominantColor(0), + _perfStatsOn(false), + _chatEntryOn(false), + _oculusTextureID(0), + _oculusProgram(0), + _oculusDistortionScale(1.25), +#ifndef _WIN32 + _audio(&_audioScope, &_myAvatar), +#endif + _stopNetworkReceiveThread(false), + _packetCount(0), + _packetsPerSecond(0), + _bytesPerSecond(0), + _bytesCount(0) { + + gettimeofday(&_applicationStartupTime, NULL); + printLog("Interface Startup:\n"); + + _voxels.setViewFrustum(&_viewFrustum); + + shared_lib::printLog = & ::printLog; + voxels_lib::printLog = & ::printLog; + avatars_lib::printLog = & ::printLog; + + unsigned int listenPort = AGENT_SOCKET_LISTEN_PORT; + const char** constArgv = const_cast(argv); + const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); + if (portStr) { + listenPort = atoi(portStr); + } + AgentList::createInstance(AGENT_TYPE_AVATAR, listenPort); + _enableNetworkThread = !cmdOptionExists(argc, constArgv, "--nonblocking"); + if (!_enableNetworkThread) { + AgentList::getInstance()->getAgentSocket().setBlocking(false); + } + + const char* domainIP = getCmdOption(argc, constArgv, "--domain"); + if (domainIP) { + strcpy(DOMAIN_IP, domainIP); + } + + // Handle Local Domain testing with the --local command line + if (cmdOptionExists(argc, constArgv, "--local")) { + printLog("Local Domain MODE!\n"); + int ip = getLocalAddress(); + sprintf(DOMAIN_IP,"%d.%d.%d.%d", (ip & 0xFF), ((ip >> 8) & 0xFF),((ip >> 16) & 0xFF), ((ip >> 24) & 0xFF)); + } + + // Check to see if the user passed in a command line option for loading a local + // Voxel File. + _voxelsFilename = getCmdOption(argc, constArgv, "-i"); + + // the callback for our instance of AgentList is attachNewHeadToAgent + AgentList::getInstance()->linkedDataCreateCallback = &attachNewHeadToAgent; + + #ifndef _WIN32 + AgentList::getInstance()->audioMixerSocketUpdate = &audioMixerUpdate; + #endif + + #ifdef _WIN32 + WSADATA WsaData; + int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); + #endif + + // start the agentList threads + AgentList::getInstance()->startSilentAgentRemovalThread(); + AgentList::getInstance()->startDomainServerCheckInThread(); + AgentList::getInstance()->startPingUnknownAgentsThread(); + + _window->setCentralWidget(_glWidget); + + initMenu(); + + QRect available = desktop()->availableGeometry(); + _window->resize(available.size()); + _window->setVisible(true); + _glWidget->setFocus(); + + // enable mouse tracking; otherwise, we only get drag events + _glWidget->setMouseTracking(true); + + // initialization continues in initializeGL when OpenGL context is ready +} + +void Application::initializeGL() { + printLog( "Created Display Window.\n" ); + + int argc = 0; + glutInit(&argc, 0); + + #ifdef _WIN32 + glewInit(); + printLog( "Glew Init complete.\n" ); + #endif + + // Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large + // field of view and near and far clip to make it interesting. + //viewFrustumOffsetCamera.setFieldOfView(90.0); + _viewFrustumOffsetCamera.setNearClip(0.1); + _viewFrustumOffsetCamera.setFarClip(500.0 * TREE_SCALE); + + initDisplay(); + printLog( "Initialized Display.\n" ); + + init(); + printLog( "Init() complete.\n" ); + + // Check to see if the user passed in a command line option for randomizing colors + bool wantColorRandomizer = !arguments().contains("--NoColorRandomizer"); + + // Check to see if the user passed in a command line option for loading a local + // Voxel File. If so, load it now. + if (!_voxelsFilename.isEmpty()) { + _voxels.loadVoxelsFile(_voxelsFilename.constData(), wantColorRandomizer); + printLog("Local Voxel File loaded.\n"); + } + + // create thread for receipt of data via UDP + if (_enableNetworkThread) { + pthread_create(&_networkReceiveThread, NULL, networkReceive, NULL); + printLog("Network receive thread created.\n"); + } + + _myAvatar.readAvatarDataFromFile(); + + // call terminate before exiting + connect(this, SIGNAL(aboutToQuit()), SLOT(terminate())); + + // call our timer function every second + QTimer* timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(timer())); + timer->start(1000); + + // call our idle function whenever we can + QTimer* idleTimer = new QTimer(this); + connect(idleTimer, SIGNAL(timeout()), SLOT(idle())); + idleTimer->start(0); +} + +void Application::paintGL() { + PerfStat("display"); + + glEnable(GL_LINE_SMOOTH); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); { + glLoadIdentity(); + + // camera settings + if (OculusManager::isConnected()) { + _myAvatar.setDisplayingHead(false); + _myCamera.setUpShift (0.0f); + _myCamera.setDistance (0.0f); + _myCamera.setTightness (100.0f); + _myCamera.setTargetPosition(_myAvatar.getHeadPosition()); + _myCamera.setTargetRotation(_myAvatar.getBodyYaw() + _myAvatar.getHeadYaw(), + -_myAvatar.getHeadPitch(), _myAvatar.getHeadRoll()); + + } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + _myCamera.setTargetPosition(_myAvatar.getSpringyHeadPosition()); + _myCamera.setTargetRotation(_myAvatar.getBodyYaw() - 180.0f, 0.0f, 0.0f); + + } else { + if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { + _myCamera.setTargetPosition(_myAvatar.getSpringyHeadPosition()); + _myCamera.setTargetRotation(_myAvatar.getAbsoluteHeadYaw() - _mouseViewShiftYaw, + _myAvatar.getRenderPitch() + _mouseViewShiftPitch, 0.0f); + + } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { + _myCamera.setTargetPosition(_myAvatar.getHeadPosition()); + _myCamera.setTargetRotation(_myAvatar.getBodyYaw() - _mouseViewShiftYaw, _mouseViewShiftPitch, 0.0f); + } + } + + // important... + _myCamera.update( 1.f/_fps ); + + // Render anything (like HUD items) that we want to be in 3D but not in worldspace + /* + const float HUD_Z_OFFSET = -5.f; + glPushMatrix(); + glm::vec3 test(0.5, 0.5, 0.5); + glTranslatef(1, 1, HUD_Z_OFFSET); + drawVector(&test); + glPopMatrix(); + */ + + + // Note: whichCamera is used to pick between the normal camera myCamera for our + // main camera, vs, an alternate camera. The alternate camera we support right now + // is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism + // to add other cameras. + // + // Why have two cameras? Well, one reason is that because in the case of the renderViewFrustum() + // code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of + // myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera + Camera whichCamera = _myCamera; + + if (_viewFrustumFromOffset->isChecked() && _frustumOn->isChecked()) { + + // set the camera to third-person view but offset so we can see the frustum + _viewFrustumOffsetCamera.setTargetYaw(_viewFrustumOffsetYaw + _myAvatar.getBodyYaw()); + _viewFrustumOffsetCamera.setPitch (_viewFrustumOffsetPitch ); + _viewFrustumOffsetCamera.setRoll (_viewFrustumOffsetRoll ); + _viewFrustumOffsetCamera.setUpShift (_viewFrustumOffsetUp ); + _viewFrustumOffsetCamera.setDistance (_viewFrustumOffsetDistance); + _viewFrustumOffsetCamera.update(1.f/_fps); + whichCamera = _viewFrustumOffsetCamera; + } + + // transform view according to whichCamera + // could be myCamera (if in normal mode) + // or could be viewFrustumOffsetCamera if in offset mode + // I changed the ordering here - roll is FIRST (JJV) + + glRotatef ( whichCamera.getRoll(), IDENTITY_FRONT.x, IDENTITY_FRONT.y, IDENTITY_FRONT.z); + glRotatef ( whichCamera.getPitch(), IDENTITY_RIGHT.x, IDENTITY_RIGHT.y, IDENTITY_RIGHT.z); + glRotatef (180.0 - whichCamera.getYaw(), IDENTITY_UP.x, IDENTITY_UP.y, IDENTITY_UP.z ); + + glTranslatef(-whichCamera.getPosition().x, -whichCamera.getPosition().y, -whichCamera.getPosition().z); + + // Setup 3D lights (after the camera transform, so that they are positioned in world space) + glEnable(GL_COLOR_MATERIAL); + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + + GLfloat light_position0[] = { 1.0, 1.0, 0.0, 0.0 }; + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + GLfloat ambient_color[] = { 0.7, 0.7, 0.8 }; + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color); + GLfloat diffuse_color[] = { 0.8, 0.7, 0.7 }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color); + GLfloat specular_color[] = { 1.0, 1.0, 1.0, 1.0}; + glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color); + + glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color); + glMateriali(GL_FRONT, GL_SHININESS, 96); + + if (_oculusOn->isChecked()) { + displayOculus(whichCamera); + + } else { + displaySide(whichCamera); + glPopMatrix(); + + displayOverlay(); + } + } + + _frameCount++; + + // If application has just started, report time from startup to now (first frame display) + if (_justStarted) { + float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime))/1000000.0; + _justStarted = false; + char title[30]; + snprintf(title, 30, "Interface: %4.2f seconds", startupTime); + _window->setWindowTitle(title); + } +} + +void Application::resizeGL(int width, int height) { + float aspectRatio = ((float)width/(float)height); // based on screen resize + + // get the lens details from the current camera + Camera& camera = _viewFrustumFromOffset->isChecked() ? _viewFrustumOffsetCamera : _myCamera; + float nearClip = camera.getNearClip(); + float farClip = camera.getFarClip(); + float fov; + + if (_oculusOn->isChecked()) { + // more magic numbers; see Oculus SDK docs, p. 32 + camera.setAspectRatio(aspectRatio *= 0.5); + camera.setFieldOfView(fov = 2 * atan((0.0468 * _oculusDistortionScale) / 0.041) * (180 / PI)); + + // resize the render texture + if (_oculusTextureID != 0) { + glBindTexture(GL_TEXTURE_2D, _oculusTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } + } else { + camera.setAspectRatio(aspectRatio); + camera.setFieldOfView(fov = 60); + } + + // Tell our viewFrustum about this change + _viewFrustum.setAspectRatio(aspectRatio); + + glViewport(0, 0, width, height); // shouldn't this account for the menu??? + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // XXXBHG - If we're in view frustum mode, then we need to do this little bit of hackery so that + // OpenGL won't clip our frustum rendering lines. This is a debug hack for sure! Basically, this makes + // the near clip a little bit closer (therefor you see more) and the far clip a little bit farther (also, + // to see more.) + if (_frustumOn->isChecked()) { + nearClip -= 0.01f; + farClip += 0.01f; + } + + // On window reshape, we need to tell OpenGL about our new setting + gluPerspective(fov,aspectRatio,nearClip,farClip); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +static void sendVoxelServerEraseAll() { + char message[100]; + sprintf(message,"%c%s",'Z',"erase all"); + int messageSize = strlen(message) + 1; + AgentList::getInstance()->broadcastToAgents((unsigned char*) message, messageSize, &AGENT_TYPE_VOXEL, 1); +} + +static void sendVoxelServerAddScene() { + char message[100]; + sprintf(message,"%c%s",'Z',"add scene"); + int messageSize = strlen(message) + 1; + AgentList::getInstance()->broadcastToAgents((unsigned char*)message, messageSize, &AGENT_TYPE_VOXEL, 1); +} + +void Application::keyPressEvent(QKeyEvent* event) { + if (_chatEntryOn) { + if (_chatEntry.keyPressEvent(event)) { + _myAvatar.setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ? + DELETE_KEY_DOWN : INSERT_KEY_DOWN); + _myAvatar.setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR)); + + } else { + _myAvatar.setChatMessage(_chatEntry.getContents()); + _chatEntry.clear(); + _chatEntryOn = false; + setMenuShortcutsEnabled(true); + } + return; + } + + bool shifted = event->modifiers().testFlag(Qt::ShiftModifier); + switch (event->key()) { + case Qt::Key_BracketLeft: + _viewFrustumOffsetYaw -= 0.5; + break; + + case Qt::Key_BracketRight: + _viewFrustumOffsetYaw += 0.5; + break; + + case Qt::Key_BraceLeft: + _viewFrustumOffsetPitch -= 0.5; + break; + + case Qt::Key_BraceRight: + _viewFrustumOffsetPitch += 0.5; + break; + + case Qt::Key_ParenLeft: + _viewFrustumOffsetRoll -= 0.5; + break; + + case Qt::Key_ParenRight: + _viewFrustumOffsetRoll += 0.5; + break; + + case Qt::Key_Less: + _viewFrustumOffsetDistance -= 0.5; + break; + + case Qt::Key_Greater: + _viewFrustumOffsetDistance += 0.5; + break; + + case Qt::Key_Comma: + _viewFrustumOffsetUp -= 0.05; + break; + + case Qt::Key_Period: + _viewFrustumOffsetUp += 0.05; + break; + + case Qt::Key_Ampersand: + _paintOn = !_paintOn; + setupPaintingVoxel(); + break; + + case Qt::Key_AsciiCircum: + shiftPaintingColor(); + break; + + case Qt::Key_Minus: + sendVoxelServerEraseAll(); + break; + + case Qt::Key_Percent: + sendVoxelServerAddScene(); + break; + + case Qt::Key_1: + _mouseMode = (_mouseMode == ADD_VOXEL_MODE) ? NO_EDIT_MODE : ADD_VOXEL_MODE; + break; + + case Qt::Key_2: + _mouseMode = (_mouseMode == DELETE_VOXEL_MODE) ? NO_EDIT_MODE : DELETE_VOXEL_MODE; + break; + + case Qt::Key_3: + _mouseMode = (_mouseMode == COLOR_VOXEL_MODE) ? NO_EDIT_MODE : COLOR_VOXEL_MODE; + break; + + case Qt::Key_4: + addVoxelInFrontOfAvatar(); + break; + + case Qt::Key_5: + _mouseVoxelScale /= 2; + break; + + case Qt::Key_6: + _mouseVoxelScale *= 2; + break; + + case Qt::Key_L: + _displayLevels = !_displayLevels; + break; + + case Qt::Key_E: + _myAvatar.setDriveKeys(UP, 1); + break; + + case Qt::Key_C: + _myAvatar.setDriveKeys(DOWN, 1); + break; + + case Qt::Key_W: + _myAvatar.setDriveKeys(FWD, 1); + break; + + case Qt::Key_S: + _myAvatar.setDriveKeys(BACK, 1); + break; + + case Qt::Key_Space: + resetSensors(); + break; + + case Qt::Key_A: + _myAvatar.setDriveKeys(ROT_LEFT, 1); + break; + + case Qt::Key_D: + _myAvatar.setDriveKeys(ROT_RIGHT, 1); + break; + + case Qt::Key_Return: + case Qt::Key_Enter: + _chatEntryOn = true; + _myAvatar.setKeyState(NO_KEY_DOWN); + _myAvatar.setChatMessage(string()); + setMenuShortcutsEnabled(false); + break; + + case Qt::Key_Up: + _myAvatar.setDriveKeys(shifted ? UP : FWD, 1); + break; + + case Qt::Key_Down: + _myAvatar.setDriveKeys(shifted ? DOWN : BACK, 1); + break; + + case Qt::Key_Left: + _myAvatar.setDriveKeys(shifted ? LEFT : ROT_LEFT, 1); + break; + + case Qt::Key_Right: + _myAvatar.setDriveKeys(shifted ? RIGHT : ROT_RIGHT, 1); + break; + + default: + event->ignore(); + break; + } +} + +void Application::keyReleaseEvent(QKeyEvent* event) { + if (_chatEntryOn) { + _myAvatar.setKeyState(NO_KEY_DOWN); + return; + } + + switch (event->key()) { + case Qt::Key_E: + _myAvatar.setDriveKeys(UP, 0); + break; + + case Qt::Key_C: + _myAvatar.setDriveKeys(DOWN, 0); + break; + + case Qt::Key_W: + _myAvatar.setDriveKeys(FWD, 0); + break; + + case Qt::Key_S: + _myAvatar.setDriveKeys(BACK, 0); + break; + + case Qt::Key_A: + _myAvatar.setDriveKeys(ROT_LEFT, 0); + break; + + case Qt::Key_D: + _myAvatar.setDriveKeys(ROT_RIGHT, 0); + break; + + case Qt::Key_Up: + _myAvatar.setDriveKeys(FWD, 0); + _myAvatar.setDriveKeys(UP, 0); + break; + + case Qt::Key_Down: + _myAvatar.setDriveKeys(BACK, 0); + _myAvatar.setDriveKeys(DOWN, 0); + break; + + case Qt::Key_Left: + _myAvatar.setDriveKeys(LEFT, 0); + _myAvatar.setDriveKeys(ROT_LEFT, 0); + break; + + case Qt::Key_Right: + _myAvatar.setDriveKeys(RIGHT, 0); + _myAvatar.setDriveKeys(ROT_RIGHT, 0); + break; + + default: + event->ignore(); + break; + } +} + +void Application::mouseMoveEvent(QMouseEvent* event) { + _mouseX = event->x(); + _mouseY = event->y(); +} + +void Application::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + _mouseX = event->x(); + _mouseY = event->y(); + _mousePressed = true; + + if (_mouseMode == ADD_VOXEL_MODE || _mouseMode == COLOR_VOXEL_MODE) { + addVoxelUnderCursor(); + + } else if (_mouseMode == DELETE_VOXEL_MODE) { + deleteVoxelUnderCursor(); + } + } else if (event->button() == Qt::RightButton && _mouseMode != NO_EDIT_MODE) { + deleteVoxelUnderCursor(); + } +} + +void Application::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + _mouseX = event->x(); + _mouseY = event->y(); + _mousePressed = false; + } +} + +void Application::wheelEvent(QWheelEvent* event) { + if (_mouseMode == NO_EDIT_MODE) { + event->ignore(); + return; + } + if (event->delta() > 0) { + _mouseVoxelScale *= 2; + } else { + _mouseVoxelScale /= 2; + } +} + +// Every second, check the frame rates and other stuff +void Application::timer() { + gettimeofday(&_timerEnd, NULL); + _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _packetsPerSecond = (float)_packetCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _bytesPerSecond = (float)_bytesCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _frameCount = 0; + _packetCount = 0; + _bytesCount = 0; + + gettimeofday(&_timerStart, NULL); + + // if we haven't detected gyros, check for them now + if (!_serialPort.active) { + _serialPort.pair(); + } +} + +static glm::vec3 getFaceVector(BoxFace face) { + switch (face) { + case MIN_X_FACE: + return glm::vec3(-1, 0, 0); + + case MAX_X_FACE: + return glm::vec3(1, 0, 0); + + case MIN_Y_FACE: + return glm::vec3(0, -1, 0); + + case MAX_Y_FACE: + return glm::vec3(0, 1, 0); + + case MIN_Z_FACE: + return glm::vec3(0, 0, -1); + + case MAX_Z_FACE: + return glm::vec3(0, 0, 1); + } +} + +//Find and return the gravity vector at this location +static glm::vec3 getGravity(const glm::vec3& pos) { + // + // For now, we'll test this with a simple global lookup, but soon we will add getting this + // from the domain/voxelserver (or something similar) + // + if ((pos.x > 0.f) && + (pos.x < 10.f) && + (pos.z > 0.f) && + (pos.z < 10.f) && + (pos.y > 0.f) && + (pos.y < 3.f)) { + // If above ground plane, turn gravity on + return glm::vec3(0.f, -1.f, 0.f); + } else { + // If flying in space, turn gravity OFF + return glm::vec3(0.f, 0.f, 0.f); + } +} + +void Application::idle() { + timeval check; + gettimeofday(&check, NULL); + + // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time + + if (diffclock(&_lastTimeIdle, &check) > IDLE_SIMULATE_MSECS) { + + float deltaTime = 1.f/_fps; + + // update behaviors for avatar hand movement: handControl takes mouse values as input, + // and gives back 3D values modulated for smooth transitioning between interaction modes. + _handControl.update(_mouseX, _mouseY); + _myAvatar.setHandMovementValues(_handControl.getValues()); + + // tell my avatar if the mouse is being pressed... + _myAvatar.setMousePressed(_mousePressed); + + // check what's under the mouse and update the mouse voxel + glm::vec3 mouseRayOrigin, mouseRayDirection; + _viewFrustum.computePickRay(_mouseX / (float)_glWidget->width(), + _mouseY / (float)_glWidget->height(), mouseRayOrigin, mouseRayDirection); + + // tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position + _myAvatar.setMouseRay(mouseRayOrigin, mouseRayDirection); + + float distance; + BoxFace face; + _mouseVoxel.s = 0.0f; + if (_mouseMode != NO_EDIT_MODE && _voxels.findRayIntersection( + mouseRayOrigin, mouseRayDirection, _mouseVoxel, distance, face)) { + // find the nearest voxel with the desired scale + if (_mouseVoxelScale > _mouseVoxel.s) { + // choose the larger voxel that encompasses the one selected + _mouseVoxel.x = _mouseVoxelScale * floorf(_mouseVoxel.x / _mouseVoxelScale); + _mouseVoxel.y = _mouseVoxelScale * floorf(_mouseVoxel.y / _mouseVoxelScale); + _mouseVoxel.z = _mouseVoxelScale * floorf(_mouseVoxel.z / _mouseVoxelScale); + _mouseVoxel.s = _mouseVoxelScale; + + } else { + glm::vec3 faceVector = getFaceVector(face); + if (_mouseVoxelScale < _mouseVoxel.s) { + // find the closest contained voxel + glm::vec3 pt = (mouseRayOrigin + mouseRayDirection * distance) / (float)TREE_SCALE - + faceVector * (_mouseVoxelScale * 0.5f); + _mouseVoxel.x = _mouseVoxelScale * floorf(pt.x / _mouseVoxelScale); + _mouseVoxel.y = _mouseVoxelScale * floorf(pt.y / _mouseVoxelScale); + _mouseVoxel.z = _mouseVoxelScale * floorf(pt.z / _mouseVoxelScale); + _mouseVoxel.s = _mouseVoxelScale; + } + if (_mouseMode == ADD_VOXEL_MODE) { + // use the face to determine the side on which to create a neighbor + _mouseVoxel.x += faceVector.x * _mouseVoxel.s; + _mouseVoxel.y += faceVector.y * _mouseVoxel.s; + _mouseVoxel.z += faceVector.z * _mouseVoxel.s; + } + } + + if (_mouseMode == COLOR_VOXEL_MODE) { + _mouseVoxel.red = 0; + _mouseVoxel.green = 255; + _mouseVoxel.blue = 0; + + } else if (_mouseMode == DELETE_VOXEL_MODE) { + // red indicates deletion + _mouseVoxel.red = 255; + _mouseVoxel.green = _mouseVoxel.blue = 0; + } + } + + // walking triggers the handControl to stop + if (_myAvatar.getMode() == AVATAR_MODE_WALKING) { + _handControl.stop(); + _mouseViewShiftYaw *= 0.9; + _mouseViewShiftPitch *= 0.9; + } + + // Read serial port interface devices + if (_serialPort.active) { + _serialPort.readData(); + } + + // Sample hardware, update view frustum if needed, and send avatar data to mixer/agents + updateAvatar(deltaTime); + + // read incoming packets from network + if (!_enableNetworkThread) { + networkReceive(0); + } + + //loop through all the remote avatars and simulate them... + AgentList* agentList = AgentList::getInstance(); + agentList->lock(); + for(AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { + if (agent->getLinkedData() != NULL) { + Avatar *avatar = (Avatar *)agent->getLinkedData(); + avatar->simulate(deltaTime); + avatar->setMouseRay(mouseRayOrigin, mouseRayDirection); + } + } + agentList->unlock(); + + _myAvatar.setGravity(getGravity(_myAvatar.getPosition())); + _myAvatar.simulate(deltaTime); + + // Update audio stats for procedural sounds + #ifndef _WIN32 + _audio.setLastAcceleration(_myAvatar.getThrust()); + _audio.setLastVelocity(_myAvatar.getVelocity()); + #endif + + _glWidget->updateGL(); + _lastTimeIdle = check; + } +} + +void Application::terminate() { + // Close serial port + // close(serial_fd); + + _myAvatar.writeAvatarDataToFile(); + + #ifndef _WIN32 + _audio.terminate(); + #endif + + if (_enableNetworkThread) { + _stopNetworkReceiveThread = true; + pthread_join(_networkReceiveThread, NULL); + } } void Application::pair() { PairingHandler::sendPairRequest(); } + +void Application::setHead(bool head) { + #ifndef _WIN32 + _audio.setMixerLoopbackFlag(head); + #endif + + if (head) { + Camera::CameraFollowingAttributes a; + a.upShift = 0.0f; + a.distance = 0.2f; + a.tightness = 100.0f; + _myCamera.setMode(CAMERA_MODE_MIRROR, a); + _myAvatar.setDisplayingHead(true); + } else { + Camera::CameraFollowingAttributes a; + a.upShift = -0.2f; + a.distance = 1.5f; + a.tightness = 8.0f; + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); + _myAvatar.setDisplayingHead(true); + } +} + +void Application::setNoise(bool noise) { + _myAvatar.setNoise(noise); +} + +void Application::setFullscreen(bool fullscreen) { + _window->setWindowState(fullscreen ? (_window->windowState() | Qt::WindowFullScreen) : + (_window->windowState() & ~Qt::WindowFullScreen)); +} + +void Application::setRenderFirstPerson(bool firstPerson) { + if (firstPerson) { + Camera::CameraFollowingAttributes a; + a.upShift = 0.0f; + a.distance = 0.0f; + a.tightness = 100.0f; + _myCamera.setMode(CAMERA_MODE_FIRST_PERSON, a); + _myAvatar.setDisplayingHead(false); + + } else { + Camera::CameraFollowingAttributes a; + a.upShift = -0.2f; + a.distance = 1.5f; + a.tightness = 8.0f; + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); + _myAvatar.setDisplayingHead(true); + } +} + +void Application::setOculus(bool oculus) { + resizeGL(_glWidget->width(), _glWidget->height()); +} + +void Application::setFrustumOffset(bool frustumOffset) { + // reshape so that OpenGL will get the right lens details for the camera of choice + resizeGL(_glWidget->width(), _glWidget->height()); +} + +void Application::cycleFrustumRenderMode() { + _frustumDrawingMode = (FrustumDrawMode)((_frustumDrawingMode + 1) % FRUSTUM_DRAW_MODE_COUNT); + updateFrustumRenderModeAction(); +} + +void Application::setRenderWarnings(bool renderWarnings) { + _voxels.setRenderPipelineWarnings(renderWarnings); +} + +void Application::doKillLocalVoxels() { + _wantToKillLocalVoxels = true; +} + +void Application::doRandomizeVoxelColors() { + _voxels.randomizeVoxelColors(); +} + +void Application::doFalseRandomizeVoxelColors() { + _voxels.falseColorizeRandom(); +} + +void Application::doFalseRandomizeEveryOtherVoxelColors() { + _voxels.falseColorizeRandomEveryOther(); +} + +void Application::doFalseColorizeByDistance() { + loadViewFrustum(_viewFrustum); + _voxels.falseColorizeDistanceFromView(&_viewFrustum); +} + +void Application::doFalseColorizeInView() { + loadViewFrustum(_viewFrustum); + // we probably want to make sure the viewFrustum is initialized first + _voxels.falseColorizeInView(&_viewFrustum); +} + +void Application::doTrueVoxelColors() { + _voxels.trueColorize(); +} + +void Application::doTreeStats() { + _voxels.collectStatsForTreesAndVBOs(); +} + +void Application::setWantsMonochrome(bool wantsMonochrome) { + _myAvatar.setWantColor(!wantsMonochrome); +} + +void Application::setWantsResIn(bool wantsResIn) { + _myAvatar.setWantResIn(wantsResIn); +} + +void Application::initMenu() { + QMenuBar* menuBar = new QMenuBar(); + _window->setMenuBar(menuBar); + + QMenu* fileMenu = menuBar->addMenu("File"); + fileMenu->addAction("Pair", this, SLOT(pair())); + fileMenu->addAction("Quit", this, SLOT(quit()), Qt::Key_Q); + + QMenu* optionsMenu = menuBar->addMenu("Options"); + (_lookingInMirror = optionsMenu->addAction("Mirror", this, SLOT(setHead(bool)), Qt::Key_H))->setCheckable(true); + optionsMenu->addAction("Noise", this, SLOT(setNoise(bool)), Qt::Key_N)->setCheckable(true); + (_gyroLook = optionsMenu->addAction("Gyro Look"))->setCheckable(true); + _gyroLook->setChecked(true); + optionsMenu->addAction("Fullscreen", this, SLOT(setFullscreen(bool)), Qt::Key_F)->setCheckable(true); + + QMenu* renderMenu = menuBar->addMenu("Render"); + (_renderVoxels = renderMenu->addAction("Voxels"))->setCheckable(true); + _renderVoxels->setChecked(true); + _renderVoxels->setShortcut(Qt::Key_V); + (_renderStarsOn = renderMenu->addAction("Stars"))->setCheckable(true); + _renderStarsOn->setChecked(true); + _renderStarsOn->setShortcut(Qt::Key_Asterisk); + (_renderAtmosphereOn = renderMenu->addAction("Atmosphere"))->setCheckable(true); + _renderAtmosphereOn->setChecked(true); + _renderAtmosphereOn->setShortcut(Qt::SHIFT | Qt::Key_A); + (_renderAvatarsOn = renderMenu->addAction("Avatars"))->setCheckable(true); + _renderAvatarsOn->setChecked(true); + renderMenu->addAction("First Person", this, SLOT(setRenderFirstPerson(bool)), Qt::Key_P)->setCheckable(true); + (_oculusOn = renderMenu->addAction("Oculus", this, SLOT(setOculus(bool)), Qt::Key_O))->setCheckable(true); + + QMenu* toolsMenu = menuBar->addMenu("Tools"); + (_renderStatsOn = toolsMenu->addAction("Stats"))->setCheckable(true); + _renderStatsOn->setShortcut(Qt::Key_Slash); + (_logOn = toolsMenu->addAction("Log"))->setCheckable(true); + _logOn->setChecked(true); + + QMenu* frustumMenu = menuBar->addMenu("Frustum"); + (_frustumOn = frustumMenu->addAction("Display Frustum"))->setCheckable(true); + _frustumOn->setShortcut(Qt::SHIFT | Qt::Key_F); + (_viewFrustumFromOffset = frustumMenu->addAction( + "Use Offset Camera", this, SLOT(setFrustumOffset(bool)), Qt::SHIFT | Qt::Key_O))->setCheckable(true); + (_cameraFrustum = frustumMenu->addAction("Switch Camera"))->setCheckable(true); + _cameraFrustum->setChecked(true); + _cameraFrustum->setShortcut(Qt::SHIFT | Qt::Key_C); + _frustumRenderModeAction = frustumMenu->addAction( + "Render Mode", this, SLOT(cycleFrustumRenderMode()), Qt::SHIFT | Qt::Key_R); + updateFrustumRenderModeAction(); + + QMenu* debugMenu = menuBar->addMenu("Debug"); + debugMenu->addAction("Show Render Pipeline Warnings", this, SLOT(setRenderWarnings(bool)))->setCheckable(true); + debugMenu->addAction("Kill Local Voxels", this, SLOT(doKillLocalVoxels())); + debugMenu->addAction("Randomize Voxel TRUE Colors", this, SLOT(doRandomizeVoxelColors())); + debugMenu->addAction("FALSE Color Voxels Randomly", this, SLOT(doFalseRandomizeVoxelColors())); + debugMenu->addAction("FALSE Color Voxel Every Other Randomly", this, SLOT(doFalseRandomizeEveryOtherVoxelColors())); + debugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance())); + debugMenu->addAction("FALSE Color Voxel Out of View", this, SLOT(doFalseColorizeInView())); + debugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors())); + debugMenu->addAction("Calculate Tree Stats", this, SLOT(doTreeStats()), Qt::SHIFT | Qt::Key_S); + debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true); + debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true); +} + +void Application::updateFrustumRenderModeAction() { + switch (_frustumDrawingMode) { + case FRUSTUM_DRAW_MODE_ALL: + _frustumRenderModeAction->setText("Render Mode - All"); + break; + case FRUSTUM_DRAW_MODE_VECTORS: + _frustumRenderModeAction->setText("Render Mode - Vectors"); + break; + case FRUSTUM_DRAW_MODE_PLANES: + _frustumRenderModeAction->setText("Render Mode - Planes"); + break; + case FRUSTUM_DRAW_MODE_NEAR_PLANE: + _frustumRenderModeAction->setText("Render Mode - Near"); + break; + case FRUSTUM_DRAW_MODE_FAR_PLANE: + _frustumRenderModeAction->setText("Render Mode - Far"); + break; + } +} + +void Application::initDisplay() { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glShadeModel (GL_SMOOTH); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_DEPTH_TEST); +} + +void Application::init() { + _voxels.init(); + _voxels.setViewerAvatar(&_myAvatar); + _voxels.setCamera(&_myCamera); + + _environment.init(); + + _handControl.setScreenDimensions(_glWidget->width(), _glWidget->height()); + + _headMouseX = _glWidget->width()/2; + _headMouseY = _glWidget->height()/2; + + _stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0); + + _myAvatar.setPosition(START_LOCATION); + Camera::CameraFollowingAttributes a; + a.upShift = -0.2f; + a.distance = 1.5f; + a.tightness = 8.0f; + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); + _myAvatar.setDisplayingHead(true); + + OculusManager::connect(); + + gettimeofday(&_timerStart, NULL); + gettimeofday(&_lastTimeIdle, NULL); +} + +static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { + unsigned char* bufferOut; + int sizeOut; + + if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ + AgentList::getInstance()->broadcastToAgents(bufferOut, sizeOut, &AGENT_TYPE_VOXEL, 1); + delete bufferOut; + } +} + +void Application::updateAvatar(float deltaTime) { + // Update my avatar's head position from gyros + _myAvatar.updateHeadFromGyros(deltaTime, &_serialPort, &_gravity); + + // Grab latest readings from the gyros + float measuredPitchRate = _serialPort.getLastPitchRate(); + float measuredYawRate = _serialPort.getLastYawRate(); + + // Update gyro-based mouse (X,Y on screen) + const float MIN_MOUSE_RATE = 1.0; + const float HORIZONTAL_PIXELS_PER_DEGREE = 2880.f / 45.f; + const float VERTICAL_PIXELS_PER_DEGREE = 1800.f / 30.f; + if (powf(measuredYawRate * measuredYawRate + + measuredPitchRate * measuredPitchRate, 0.5) > MIN_MOUSE_RATE) + { + _headMouseX += measuredYawRate * HORIZONTAL_PIXELS_PER_DEGREE * deltaTime; + _headMouseY -= measuredPitchRate * VERTICAL_PIXELS_PER_DEGREE * deltaTime; + } + _headMouseX = max(_headMouseX, 0); + _headMouseX = min(_headMouseX, _glWidget->width()); + _headMouseY = max(_headMouseY, 0); + _headMouseY = min(_headMouseY, _glWidget->height()); + + // Update head and body pitch and yaw based on measured gyro rates + if (_gyroLook->isChecked()) { + // Render Yaw + float renderYawSpring = fabs(_headMouseX - _glWidget->width() / 2.f) / (_glWidget->width() / 2.f); + const float RENDER_YAW_MULTIPLY = 4.f; + _myAvatar.setRenderYaw((1.f - renderYawSpring * deltaTime) * _myAvatar.getRenderYaw() + + renderYawSpring * deltaTime * -_myAvatar.getHeadYaw() * RENDER_YAW_MULTIPLY); + // Render Pitch + float renderPitchSpring = fabs(_headMouseY - _glWidget->height() / 2.f) / (_glWidget->height() / 2.f); + const float RENDER_PITCH_MULTIPLY = 4.f; + _myAvatar.setRenderPitch((1.f - renderPitchSpring * deltaTime) * _myAvatar.getRenderPitch() + + renderPitchSpring * deltaTime * -_myAvatar.getHeadPitch() * RENDER_PITCH_MULTIPLY); + } + + + if (USING_MOUSE_VIEW_SHIFT) + { + //make it so that when your mouse hits the edge of the screen, the camera shifts + float rightBoundary = (float)_glWidget->width() - MOUSE_VIEW_SHIFT_YAW_MARGIN; + float bottomBoundary = (float)_glWidget->height() - MOUSE_VIEW_SHIFT_PITCH_MARGIN; + + if (_mouseX > rightBoundary) { + float f = (_mouseX - rightBoundary) / ( (float)_glWidget->width() - rightBoundary); + _mouseViewShiftYaw += MOUSE_VIEW_SHIFT_RATE * f * deltaTime; + if (_mouseViewShiftYaw > MOUSE_VIEW_SHIFT_YAW_LIMIT) { _mouseViewShiftYaw = MOUSE_VIEW_SHIFT_YAW_LIMIT; } + } else if (_mouseX < MOUSE_VIEW_SHIFT_YAW_MARGIN) { + float f = 1.0 - (_mouseX / MOUSE_VIEW_SHIFT_YAW_MARGIN); + _mouseViewShiftYaw -= MOUSE_VIEW_SHIFT_RATE * f * deltaTime; + if (_mouseViewShiftYaw < -MOUSE_VIEW_SHIFT_YAW_LIMIT) { _mouseViewShiftYaw = -MOUSE_VIEW_SHIFT_YAW_LIMIT; } + } + if (_mouseY < MOUSE_VIEW_SHIFT_PITCH_MARGIN) { + float f = 1.0 - (_mouseY / MOUSE_VIEW_SHIFT_PITCH_MARGIN); + _mouseViewShiftPitch += MOUSE_VIEW_SHIFT_RATE * f * deltaTime; + if (_mouseViewShiftPitch > MOUSE_VIEW_SHIFT_PITCH_LIMIT ) { _mouseViewShiftPitch = MOUSE_VIEW_SHIFT_PITCH_LIMIT; } + } + else if (_mouseY > bottomBoundary) { + float f = (_mouseY - bottomBoundary) / ((float)_glWidget->height() - bottomBoundary); + _mouseViewShiftPitch -= MOUSE_VIEW_SHIFT_RATE * f * deltaTime; + if (_mouseViewShiftPitch < -MOUSE_VIEW_SHIFT_PITCH_LIMIT) { _mouseViewShiftPitch = -MOUSE_VIEW_SHIFT_PITCH_LIMIT; } + } + } + + if (OculusManager::isConnected()) { + float yaw, pitch, roll; + OculusManager::getEulerAngles(yaw, pitch, roll); + + _myAvatar.setHeadYaw(-yaw); + _myAvatar.setHeadPitch(pitch); + _myAvatar.setHeadRoll(roll); + } + + // Get audio loudness data from audio input device + #ifndef _WIN32 + _myAvatar.setLoudness(_audio.getInputLoudness()); + #endif + + // Update Avatar with latest camera and view frustum data... + // NOTE: we get this from the view frustum, to make it simpler, since the + // loadViewFrumstum() method will get the correct details from the camera + // We could optimize this to not actually load the viewFrustum, since we don't + // actually need to calculate the view frustum planes to send these details + // to the server. + loadViewFrustum(_viewFrustum); + _myAvatar.setCameraPosition(_viewFrustum.getPosition()); + _myAvatar.setCameraDirection(_viewFrustum.getDirection()); + _myAvatar.setCameraUp(_viewFrustum.getUp()); + _myAvatar.setCameraRight(_viewFrustum.getRight()); + _myAvatar.setCameraFov(_viewFrustum.getFieldOfView()); + _myAvatar.setCameraAspectRatio(_viewFrustum.getAspectRatio()); + _myAvatar.setCameraNearClip(_viewFrustum.getNearClip()); + _myAvatar.setCameraFarClip(_viewFrustum.getFarClip()); + + AgentList* agentList = AgentList::getInstance(); + if (agentList->getOwnerID() != UNKNOWN_AGENT_ID) { + // if I know my ID, send head/hand data to the avatar mixer and voxel server + unsigned char broadcastString[200]; + unsigned char* endOfBroadcastStringWrite = broadcastString; + + *(endOfBroadcastStringWrite++) = PACKET_HEADER_HEAD_DATA; + endOfBroadcastStringWrite += packAgentId(endOfBroadcastStringWrite, agentList->getOwnerID()); + + endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); + + const char broadcastReceivers[2] = {AGENT_TYPE_VOXEL, AGENT_TYPE_AVATAR_MIXER}; + AgentList::getInstance()->broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, broadcastReceivers, sizeof(broadcastReceivers)); + } + + // If I'm in paint mode, send a voxel out to VOXEL server agents. + if (_paintOn) { + + glm::vec3 avatarPos = _myAvatar.getPosition(); + + // For some reason, we don't want to flip X and Z here. + _paintingVoxel.x = avatarPos.x / 10.0; + _paintingVoxel.y = avatarPos.y / 10.0; + _paintingVoxel.z = avatarPos.z / 10.0; + + if (_paintingVoxel.x >= 0.0 && _paintingVoxel.x <= 1.0 && + _paintingVoxel.y >= 0.0 && _paintingVoxel.y <= 1.0 && + _paintingVoxel.z >= 0.0 && _paintingVoxel.z <= 1.0) { + + sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, _paintingVoxel); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////// +// loadViewFrustum() +// +// Description: this will load the view frustum bounds for EITHER the head +// or the "myCamera". +// +void Application::loadViewFrustum(ViewFrustum& viewFrustum) { + // We will use these below, from either the camera or head vectors calculated above + glm::vec3 position; + glm::vec3 direction; + glm::vec3 up; + glm::vec3 right; + float fov, nearClip, farClip; + + // Camera or Head? + if (_cameraFrustum->isChecked()) { + position = _myCamera.getPosition(); + } else { + position = _myAvatar.getHeadPosition(); + } + + fov = _myCamera.getFieldOfView(); + nearClip = _myCamera.getNearClip(); + farClip = _myCamera.getFarClip(); + + Orientation o = _myCamera.getOrientation(); + + direction = o.getFront(); + up = o.getUp(); + right = o.getRight(); + + /* + printf("position.x=%f, position.y=%f, position.z=%f\n", position.x, position.y, position.z); + printf("yaw=%f, pitch=%f, roll=%f\n", yaw,pitch,roll); + printf("direction.x=%f, direction.y=%f, direction.z=%f\n", direction.x, direction.y, direction.z); + printf("up.x=%f, up.y=%f, up.z=%f\n", up.x, up.y, up.z); + printf("right.x=%f, right.y=%f, right.z=%f\n", right.x, right.y, right.z); + printf("fov=%f\n", fov); + printf("nearClip=%f\n", nearClip); + printf("farClip=%f\n", farClip); + */ + + // Set the viewFrustum up with the correct position and orientation of the camera + viewFrustum.setPosition(position); + viewFrustum.setOrientation(direction,up,right); + + // Also make sure it's got the correct lens details from the camera + viewFrustum.setFieldOfView(fov); + viewFrustum.setNearClip(nearClip); + viewFrustum.setFarClip(farClip); + + // Ask the ViewFrustum class to calculate our corners + viewFrustum.calculate(); +} + +// this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample +// code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license +// (http://www.apache.org/licenses/LICENSE-2.0) +static const char* DISTORTION_FRAGMENT_SHADER = + "#version 120\n" + "uniform sampler2D texture;" + "uniform vec2 lensCenter;" + "uniform vec2 screenCenter;" + "uniform vec2 scale;" + "uniform vec2 scaleIn;" + "uniform vec4 hmdWarpParam;" + "vec2 hmdWarp(vec2 in01) {" + " vec2 theta = (in01 - lensCenter) * scaleIn;" + " float rSq = theta.x * theta.x + theta.y * theta.y;" + " vec2 theta1 = theta * (hmdWarpParam.x + hmdWarpParam.y * rSq + " + " hmdWarpParam.z * rSq * rSq + hmdWarpParam.w * rSq * rSq * rSq);" + " return lensCenter + scale * theta1;" + "}" + "void main(void) {" + " vec2 tc = hmdWarp(gl_TexCoord[0].st);" + " vec2 below = step(screenCenter.st + vec2(-0.25, -0.5), tc.st);" + " vec2 above = vec2(1.0, 1.0) - step(screenCenter.st + vec2(0.25, 0.5), tc.st);" + " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texture2D(texture, tc), " + " above.s * above.t * below.s * below.t);" + "}"; + +void Application::displayOculus(Camera& whichCamera) { + // magic numbers ahoy! in order to avoid pulling in the Oculus utility library that calculates + // the rendering parameters from the hardware stats, i just folded their calculations into + // constants using the stats for the current-model hardware as contained in the SDK file + // LibOVR/Src/Util/Util_Render_Stereo.cpp + + // eye + + // render the left eye view to the left side of the screen + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(0.151976, 0, 0); // +h, see Oculus SDK docs p. 26 + gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), + whichCamera.getNearClip(), whichCamera.getFarClip()); + glTranslatef(0.032, 0, 0); // dip/2, see p. 27 + + glMatrixMode(GL_MODELVIEW); + glViewport(0, 0, _glWidget->width() / 2, _glWidget->height()); + displaySide(whichCamera); + + // and the right eye to the right side + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glTranslatef(-0.151976, 0, 0); // -h + gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), + whichCamera.getNearClip(), whichCamera.getFarClip()); + glTranslatef(-0.032, 0, 0); + + glMatrixMode(GL_MODELVIEW); + glViewport(_glWidget->width() / 2, 0, _glWidget->width() / 2, _glWidget->height()); + displaySide(whichCamera); + + glPopMatrix(); + + // restore our normal viewport + glViewport(0, 0, _glWidget->width(), _glWidget->height()); + + if (_oculusTextureID == 0) { + glGenTextures(1, &_oculusTextureID); + glBindTexture(GL_TEXTURE_2D, _oculusTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _glWidget->width(), _glWidget->height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + _oculusProgram = new ProgramObject(); + _oculusProgram->attachFromSourceCode(GL_FRAGMENT_SHADER_ARB, DISTORTION_FRAGMENT_SHADER); + _oculusProgram->link(); + + _textureLocation = _oculusProgram->getUniformLocation("texture"); + _lensCenterLocation = _oculusProgram->getUniformLocation("lensCenter"); + _screenCenterLocation = _oculusProgram->getUniformLocation("screenCenter"); + _scaleLocation = _oculusProgram->getUniformLocation("scale"); + _scaleInLocation = _oculusProgram->getUniformLocation("scaleIn"); + _hmdWarpParamLocation = _oculusProgram->getUniformLocation("hmdWarpParam"); + + } else { + glBindTexture(GL_TEXTURE_2D, _oculusTextureID); + } + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _glWidget->width(), _glWidget->height()); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0, _glWidget->width(), 0, _glWidget->height()); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + // for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp + + float scaleFactor = 1.0 / _oculusDistortionScale; + float aspectRatio = (_glWidget->width() * 0.5) / _glWidget->height(); + + glDisable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + _oculusProgram->bind(); + _oculusProgram->setUniform(_textureLocation, 0); + _oculusProgram->setUniform(_lensCenterLocation, 0.287994, 0.5); // see SDK docs, p. 29 + _oculusProgram->setUniform(_screenCenterLocation, 0.25, 0.5); + _oculusProgram->setUniform(_scaleLocation, 0.25 * scaleFactor, 0.5 * scaleFactor * aspectRatio); + _oculusProgram->setUniform(_scaleInLocation, 4, 2 / aspectRatio); + _oculusProgram->setUniform(_hmdWarpParamLocation, 1.0, 0.22, 0.24, 0); + + glColor3f(1, 0, 1); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(0, 0); + glTexCoord2f(0.5, 0); + glVertex2f(_glWidget->width()/2, 0); + glTexCoord2f(0.5, 1); + glVertex2f(_glWidget->width() / 2, _glWidget->height()); + glTexCoord2f(0, 1); + glVertex2f(0, _glWidget->height()); + glEnd(); + + _oculusProgram->setUniform(_lensCenterLocation, 0.787994, 0.5); + _oculusProgram->setUniform(_screenCenterLocation, 0.75, 0.5); + + glBegin(GL_QUADS); + glTexCoord2f(0.5, 0); + glVertex2f(_glWidget->width() / 2, 0); + glTexCoord2f(1, 0); + glVertex2f(_glWidget->width(), 0); + glTexCoord2f(1, 1); + glVertex2f(_glWidget->width(), _glWidget->height()); + glTexCoord2f(0.5, 1); + glVertex2f(_glWidget->width() / 2, _glWidget->height()); + glEnd(); + + glEnable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + _oculusProgram->release(); + + glPopMatrix(); +} + +void Application::displaySide(Camera& whichCamera) { + glPushMatrix(); + + if (_renderStarsOn->isChecked()) { + // should be the first rendering pass - w/o depth buffer / lighting + + // compute starfield alpha based on distance from atmosphere + float alpha = 1.0f; + if (_renderAtmosphereOn->isChecked()) { + float height = glm::distance(whichCamera.getPosition(), _environment.getAtmosphereCenter()); + if (height < _environment.getAtmosphereInnerRadius()) { + alpha = 0.0f; + + } else if (height < _environment.getAtmosphereOuterRadius()) { + alpha = (height - _environment.getAtmosphereInnerRadius()) / + (_environment.getAtmosphereOuterRadius() - _environment.getAtmosphereInnerRadius()); + } + } + + // finally render the starfield + _stars.render(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), alpha); + } + + // draw the sky dome + if (_renderAtmosphereOn->isChecked()) { + _environment.renderAtmosphere(whichCamera); + } + + glEnable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + + // draw a red sphere + float sphereRadius = 0.25f; + glColor3f(1,0,0); + glPushMatrix(); + glutSolidSphere(sphereRadius, 15, 15); + glPopMatrix(); + + //draw a grid ground plane.... + drawGroundPlaneGrid(10.f); + + // Draw voxels + if (_renderVoxels->isChecked()) { + _voxels.render(); + } + + // indicate what we'll be adding/removing in mouse mode, if anything + if (_mouseVoxel.s != 0) { + glPushMatrix(); + if (_mouseMode == ADD_VOXEL_MODE) { + // use a contrasting color so that we can see what we're doing + glColor3ub(_mouseVoxel.red + 128, _mouseVoxel.green + 128, _mouseVoxel.blue + 128); + } else { + glColor3ub(_mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue); + } + glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); + glTranslatef(_mouseVoxel.x + _mouseVoxel.s*0.5f, + _mouseVoxel.y + _mouseVoxel.s*0.5f, + _mouseVoxel.z + _mouseVoxel.s*0.5f); + glLineWidth(4.0f); + glutWireCube(_mouseVoxel.s); + glLineWidth(1.0f); + glPopMatrix(); + } + + if (_renderAvatarsOn->isChecked()) { + // Render avatars of other agents + AgentList* agentList = AgentList::getInstance(); + agentList->lock(); + for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { + if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) { + Avatar *avatar = (Avatar *)agent->getLinkedData(); + avatar->render(0, _myCamera.getPosition()); + } + } + agentList->unlock(); + + // Render my own Avatar + _myAvatar.render(_lookingInMirror, _myCamera.getPosition()); + } + + // Render the world box + if (!_lookingInMirror->isChecked() && _renderStatsOn->isChecked()) { render_world_box(); } + + // brad's frustum for debugging + if (_frustumOn->isChecked()) renderViewFrustum(_viewFrustum); + + glPopMatrix(); +} + +void Application::displayOverlay() { + // Render 2D overlay: I/O level bar graphs and text + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + gluOrtho2D(0, _glWidget->width(), _glWidget->height(), 0); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + #ifndef _WIN32 + _audio.render(_glWidget->width(), _glWidget->height()); + _audioScope.render(20, _glWidget->height() - 200); + #endif + + //noiseTest(_glWidget->width(), _glWidget->height()); + + if (DISPLAY_HEAD_MOUSE && !_lookingInMirror->isChecked() && USING_INVENSENSE_MPU9150) { + // Display small target box at center or head mouse target that can also be used to measure LOD + glColor3f(1.0, 1.0, 1.0); + glDisable(GL_LINE_SMOOTH); + const int PIXEL_BOX = 20; + glBegin(GL_LINE_STRIP); + glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY - PIXEL_BOX/2); + glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY - PIXEL_BOX/2); + glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY + PIXEL_BOX/2); + glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY + PIXEL_BOX/2); + glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY - PIXEL_BOX/2); + glEnd(); + glEnable(GL_LINE_SMOOTH); + } + + // Show detected levels from the serial I/O ADC channel sensors + if (_displayLevels) _serialPort.renderLevels(_glWidget->width(), _glWidget->height()); + + // Display stats and log text onscreen + glLineWidth(1.0f); + glPointSize(1.0f); + + if (_renderStatsOn->isChecked()) { displayStats(); } + if (_logOn->isChecked()) { logger.render(_glWidget->width(), _glWidget->height()); } + + // Show chat entry field + if (_chatEntryOn) { + _chatEntry.render(_glWidget->width(), _glWidget->height()); + } + + // Stats at upper right of screen about who domain server is telling us about + glPointSize(1.0f); + char agents[100]; + + AgentList* agentList = AgentList::getInstance(); + int totalAvatars = 0, totalServers = 0; + + for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { + agent->getType() == AGENT_TYPE_AVATAR ? totalAvatars++ : totalServers++; + } + + sprintf(agents, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars); + drawtext(_glWidget->width() - 150, 20, 0.10, 0, 1.0, 0, agents, 1, 0, 0); + + if (_paintOn) { + + char paintMessage[100]; + sprintf(paintMessage,"Painting (%.3f,%.3f,%.3f/%.3f/%d,%d,%d)", + _paintingVoxel.x, _paintingVoxel.y, _paintingVoxel.z, _paintingVoxel.s, + (unsigned int)_paintingVoxel.red, (unsigned int)_paintingVoxel.green, (unsigned int)_paintingVoxel.blue); + drawtext(_glWidget->width() - 350, 50, 0.10, 0, 1.0, 0, paintMessage, 1, 1, 0); + } + + glPopMatrix(); +} + +void Application::displayStats() { + int statsVerticalOffset = 8; + + char stats[200]; + sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps", + _fps, _packetsPerSecond, (float)_bytesPerSecond * 8.f / 1000000.f); + drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, stats); + + std::stringstream voxelStats; + voxelStats.precision(4); + voxelStats << "Voxels Rendered: " << _voxels.getVoxelsRendered() / 1000.f << "K Updated: " << _voxels.getVoxelsUpdated()/1000.f << "K"; + drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + voxelStats.str(""); + voxelStats << "Voxels Created: " << _voxels.getVoxelsCreated() / 1000.f << "K (" << _voxels.getVoxelsCreatedPerSecondAverage() / 1000.f + << "Kps) "; + drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + voxelStats.str(""); + voxelStats << "Voxels Colored: " << _voxels.getVoxelsColored() / 1000.f << "K (" << _voxels.getVoxelsColoredPerSecondAverage() / 1000.f + << "Kps) "; + drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + voxelStats.str(""); + voxelStats << "Voxel Bits Read: " << _voxels.getVoxelsBytesRead() * 8.f / 1000000.f + << "M (" << _voxels.getVoxelsBytesReadPerSecondAverage() * 8.f / 1000000.f << " Mbps)"; + drawtext(10, statsVerticalOffset + 290,0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + voxelStats.str(""); + float voxelsBytesPerColored = _voxels.getVoxelsColored() + ? ((float) _voxels.getVoxelsBytesRead() / _voxels.getVoxelsColored()) + : 0; + + voxelStats << "Voxels Bits per Colored: " << voxelsBytesPerColored * 8; + drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + Agent *avatarMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AVATAR_MIXER); + char avatarMixerStats[200]; + + if (avatarMixer) { + sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", + roundf(avatarMixer->getAverageKilobitsPerSecond()), + roundf(avatarMixer->getAveragePacketsPerSecond())); + } else { + sprintf(avatarMixerStats, "No Avatar Mixer"); + } + + drawtext(10, statsVerticalOffset + 330, 0.10f, 0, 1.0, 0, avatarMixerStats); + + if (_perfStatsOn) { + // Get the PerfStats group details. We need to allocate and array of char* long enough to hold 1+groups + char** perfStatLinesArray = new char*[PerfStat::getGroupCount()+1]; + int lines = PerfStat::DumpStats(perfStatLinesArray); + int atZ = 150; // arbitrary place on screen that looks good + for (int line=0; line < lines; line++) { + drawtext(10, statsVerticalOffset + atZ, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]); + delete perfStatLinesArray[line]; // we're responsible for cleanup + perfStatLinesArray[line]=NULL; + atZ+=20; // height of a line + } + delete []perfStatLinesArray; // we're responsible for cleanup + } +} + +///////////////////////////////////////////////////////////////////////////////////// +// renderViewFrustum() +// +// Description: this will render the view frustum bounds for EITHER the head +// or the "myCamera". +// +// Frustum rendering mode. For debug purposes, we allow drawing the frustum in a couple of different ways. +// We can draw it with each of these parts: +// * Origin Direction/Up/Right vectors - these will be drawn at the point of the camera +// * Near plane - this plane is drawn very close to the origin point. +// * Right/Left planes - these two planes are drawn between the near and far planes. +// * Far plane - the plane is drawn in the distance. +// Modes - the following modes, will draw the following parts. +// * All - draws all the parts listed above +// * Planes - draws the planes but not the origin vectors +// * Origin Vectors - draws the origin vectors ONLY +// * Near Plane - draws only the near plane +// * Far Plane - draws only the far plane +void Application::renderViewFrustum(ViewFrustum& viewFrustum) { + // Load it with the latest details! + loadViewFrustum(viewFrustum); + + glm::vec3 position = viewFrustum.getPosition(); + glm::vec3 direction = viewFrustum.getDirection(); + glm::vec3 up = viewFrustum.getUp(); + glm::vec3 right = viewFrustum.getRight(); + + // Get ready to draw some lines + glDisable(GL_LIGHTING); + glColor4f(1.0, 1.0, 1.0, 1.0); + glLineWidth(1.0); + glBegin(GL_LINES); + + if (_frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || _frustumDrawingMode == FRUSTUM_DRAW_MODE_VECTORS) { + // Calculate the origin direction vectors + glm::vec3 lookingAt = position + (direction * 0.2f); + glm::vec3 lookingAtUp = position + (up * 0.2f); + glm::vec3 lookingAtRight = position + (right * 0.2f); + + // Looking At = white + glColor3f(1,1,1); + glVertex3f(position.x, position.y, position.z); + glVertex3f(lookingAt.x, lookingAt.y, lookingAt.z); + + // Looking At Up = purple + glColor3f(1,0,1); + glVertex3f(position.x, position.y, position.z); + glVertex3f(lookingAtUp.x, lookingAtUp.y, lookingAtUp.z); + + // Looking At Right = cyan + glColor3f(0,1,1); + glVertex3f(position.x, position.y, position.z); + glVertex3f(lookingAtRight.x, lookingAtRight.y, lookingAtRight.z); + } + + if (_frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || _frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES + || _frustumDrawingMode == FRUSTUM_DRAW_MODE_NEAR_PLANE) { + // Drawing the bounds of the frustum + // viewFrustum.getNear plane - bottom edge + glColor3f(1,0,0); + glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); + glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); + + // viewFrustum.getNear plane - top edge + glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); + glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); + + // viewFrustum.getNear plane - right edge + glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); + glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); + + // viewFrustum.getNear plane - left edge + glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); + glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); + } + + if (_frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || _frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES + || _frustumDrawingMode == FRUSTUM_DRAW_MODE_FAR_PLANE) { + // viewFrustum.getFar plane - bottom edge + glColor3f(0,1,0); // GREEN!!! + glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); + glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); + + // viewFrustum.getFar plane - top edge + glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); + glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); + + // viewFrustum.getFar plane - right edge + glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); + glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); + + // viewFrustum.getFar plane - left edge + glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); + glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); + } + + if (_frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || _frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES) { + // RIGHT PLANE IS CYAN + // right plane - bottom edge - viewFrustum.getNear to distant + glColor3f(0,1,1); + glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); + glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); + + // right plane - top edge - viewFrustum.getNear to distant + glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); + glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); + + // LEFT PLANE IS BLUE + // left plane - bottom edge - viewFrustum.getNear to distant + glColor3f(0,0,1); + glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); + glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); + + // left plane - top edge - viewFrustum.getNear to distant + glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); + glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); + } + + glEnd(); + glEnable(GL_LIGHTING); +} + +void Application::setupPaintingVoxel() { + glm::vec3 avatarPos = _myAvatar.getPosition(); + + _paintingVoxel.x = avatarPos.z/-10.0; // voxel space x is negative z head space + _paintingVoxel.y = avatarPos.y/-10.0; // voxel space y is negative y head space + _paintingVoxel.z = avatarPos.x/-10.0; // voxel space z is negative x head space + _paintingVoxel.s = 1.0/256; + + shiftPaintingColor(); +} + +void Application::shiftPaintingColor() { + // About the color of the paintbrush... first determine the dominant color + _dominantColor = (_dominantColor + 1) % 3; // 0=red,1=green,2=blue + _paintingVoxel.red = (_dominantColor == 0) ? randIntInRange(200, 255) : randIntInRange(40, 100); + _paintingVoxel.green = (_dominantColor == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100); + _paintingVoxel.blue = (_dominantColor == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100); +} + +void Application::addVoxelInFrontOfAvatar() { + VoxelDetail detail; + + glm::vec3 position = (_myAvatar.getPosition() + _myAvatar.getCameraDirection()) * (1.0f / TREE_SCALE); + detail.s = _mouseVoxelScale; + + detail.x = detail.s * floor(position.x / detail.s); + detail.y = detail.s * floor(position.y / detail.s); + detail.z = detail.s * floor(position.z / detail.s); + detail.red = 128; + detail.green = 128; + detail.blue = 128; + + sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, detail); + + // create the voxel locally so it appears immediately + _voxels.createVoxel(detail.x, detail.y, detail.z, detail.s, detail.red, detail.green, detail.blue); +} + +void Application::addVoxelUnderCursor() { + if (_mouseVoxel.s != 0) { + sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, _mouseVoxel); + + // create the voxel locally so it appears immediately + _voxels.createVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s, + _mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue); + } +} + +void Application::deleteVoxelUnderCursor() { + if (_mouseVoxel.s != 0) { + sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, _mouseVoxel); + + // delete the voxel locally so it disappears immediately + _voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + } +} + +void Application::resetSensors() { + _myAvatar.setPosition(START_LOCATION); + _headMouseX = _glWidget->width() / 2; + _headMouseY = _glWidget->height() / 2; + + if (_serialPort.active) { + _serialPort.resetAverages(); + } + _myAvatar.reset(); +} + +static void setShortcutsEnabled(QWidget* widget, bool enabled) { + foreach (QAction* action, widget->actions()) { + QKeySequence shortcut = action->shortcut(); + if (!shortcut.isEmpty() && (shortcut[0] & (Qt::CTRL | Qt::ALT | Qt::META)) == 0) { + // it's a shortcut that may coincide with a "regular" key, so switch its context + action->setShortcutContext(enabled ? Qt::WindowShortcut : Qt::WidgetShortcut); + } + } + foreach (QObject* child, widget->children()) { + if (child->isWidgetType()) { + setShortcutsEnabled(static_cast(child), enabled); + } + } +} + +void Application::setMenuShortcutsEnabled(bool enabled) { + setShortcutsEnabled(_window->menuBar(), enabled); +} + +void Application::attachNewHeadToAgent(Agent *newAgent) { + if (newAgent->getLinkedData() == NULL) { + newAgent->setLinkedData(new Avatar(false)); + } +} + +#ifndef _WIN32 +void Application::audioMixerUpdate(in_addr_t newMixerAddress, in_port_t newMixerPort) { + static_cast(QCoreApplication::instance())->_audio.updateMixerParams(newMixerAddress, newMixerPort); +} +#endif + +// Receive packets from other agents/servers and decide what to do with them! +void* Application::networkReceive(void* args) { + sockaddr senderAddress; + ssize_t bytesReceived; + + Application* app = static_cast(QCoreApplication::instance()); + while (!app->_stopNetworkReceiveThread) { + // check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that + if (app->_wantToKillLocalVoxels) { + app->_voxels.killLocalVoxels(); + app->_wantToKillLocalVoxels = false; + } + + if (AgentList::getInstance()->getAgentSocket().receive(&senderAddress, app->_incomingPacket, &bytesReceived)) { + app->_packetCount++; + app->_bytesCount += bytesReceived; + + switch (app->_incomingPacket[0]) { + case PACKET_HEADER_TRANSMITTER_DATA: + // Process UDP packets that are sent to the client from local sensor devices + app->_myAvatar.processTransmitterData(app->_incomingPacket, bytesReceived); + break; + case PACKET_HEADER_VOXEL_DATA: + case PACKET_HEADER_VOXEL_DATA_MONOCHROME: + case PACKET_HEADER_Z_COMMAND: + case PACKET_HEADER_ERASE_VOXEL: + app->_voxels.parseData(app->_incomingPacket, bytesReceived); + break; + case PACKET_HEADER_ENVIRONMENT_DATA: + app->_environment.parseData(app->_incomingPacket, bytesReceived); + break; + case PACKET_HEADER_BULK_AVATAR_DATA: + AgentList::getInstance()->processBulkAgentData(&senderAddress, + app->_incomingPacket, + bytesReceived); + break; + default: + AgentList::getInstance()->processAgentData(&senderAddress, app->_incomingPacket, bytesReceived); + break; + } + } else if (!app->_enableNetworkThread) { + break; + } + } + + if (app->_enableNetworkThread) { + pthread_exit(0); + } + return NULL; +} + diff --git a/interface/src/Application.h b/interface/src/Application.h index 0aab27329e..035f705b41 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -9,16 +9,223 @@ #ifndef __interface__Application__ #define __interface__Application__ +#include +#include + #include +#include + +#ifndef _WIN32 +#include "Audio.h" +#endif + +#include "Camera.h" +#include "Environment.h" +#include "HandControl.h" +#include "SerialInterface.h" +#include "Stars.h" +#include "ViewFrustum.h" +#include "VoxelSystem.h" +#include "ui/ChatEntry.h" + +class QAction; +class QGLWidget; +class QKeyEvent; +class QMainWindow; +class QMouseEvent; +class QWheelEvent; + +class Agent; +class ProgramObject; + class Application : public QApplication { Q_OBJECT public: Application(int& argc, char** argv); -public slots: + void initializeGL(); + void paintGL(); + void resizeGL(int width, int height); + + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + + void mouseMoveEvent(QMouseEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + + void wheelEvent(QWheelEvent* event); + +private slots: + + void timer(); + void idle(); + void terminate(); + void pair(); + + void setHead(bool head); + void setNoise(bool noise); + void setFullscreen(bool fullscreen); + + void setRenderFirstPerson(bool firstPerson); + void setOculus(bool oculus); + + void setFrustumOffset(bool frustumOffset); + void cycleFrustumRenderMode(); + + void setRenderWarnings(bool renderWarnings); + void doKillLocalVoxels(); + void doRandomizeVoxelColors(); + void doFalseRandomizeVoxelColors(); + void doFalseRandomizeEveryOtherVoxelColors(); + void doFalseColorizeByDistance(); + void doFalseColorizeInView(); + void doTrueVoxelColors(); + void doTreeStats(); + void setWantsMonochrome(bool wantsMonochrome); + void setWantsResIn(bool wantsResIn); + +private: + + void initMenu(); + void updateFrustumRenderModeAction(); + void initDisplay(); + void init(); + + void updateAvatar(float deltaTime); + void loadViewFrustum(ViewFrustum& viewFrustum); + + void displayOculus(Camera& whichCamera); + void displaySide(Camera& whichCamera); + void displayOverlay(); + void displayStats(); + + void renderViewFrustum(ViewFrustum& viewFrustum); + + void setupPaintingVoxel(); + void shiftPaintingColor(); + void addVoxelInFrontOfAvatar(); + void addVoxelUnderCursor(); + void deleteVoxelUnderCursor(); + + void resetSensors(); + + void setMenuShortcutsEnabled(bool enabled); + + static void attachNewHeadToAgent(Agent *newAgent); + #ifndef _WIN32 + static void audioMixerUpdate(in_addr_t newMixerAddress, in_port_t newMixerPort); + #endif + static void* networkReceive(void* args); + + QMainWindow* _window; + QGLWidget* _glWidget; + + QAction* _lookingInMirror; // Are we currently rendering one's own head as if in mirror? + QAction* _gyroLook; // Whether to allow the gyro data from head to move your view + QAction* _renderVoxels; // Whether to render voxels + QAction* _renderStarsOn; // Whether to display the stars + QAction* _renderAtmosphereOn; // Whether to display the atmosphere + QAction* _renderAvatarsOn; // Whether to render avatars + QAction* _oculusOn; // Whether to configure the display for the Oculus Rift + QAction* _renderStatsOn; // Whether to show onscreen text overlay with stats + QAction* _logOn; // Whether to show on-screen log + QAction* _frustumOn; // Whether or not to display the debug view frustum + QAction* _viewFrustumFromOffset; // Whether or not to offset the view of the frustum + QAction* _cameraFrustum; // which frustum to look at + QAction* _frustumRenderModeAction; + + SerialInterface _serialPort; + bool _displayLevels; + + glm::vec3 _gravity; + + // Frame Rate Measurement + int _frameCount; + float _fps; + timeval _applicationStartupTime; + timeval _timerStart, _timerEnd; + timeval _lastTimeIdle; + bool _justStarted; + + Stars _stars; + + VoxelSystem _voxels; + QByteArray _voxelsFilename; + bool _wantToKillLocalVoxels; + + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + + enum FrustumDrawMode { FRUSTUM_DRAW_MODE_ALL, FRUSTUM_DRAW_MODE_VECTORS, FRUSTUM_DRAW_MODE_PLANES, + FRUSTUM_DRAW_MODE_NEAR_PLANE, FRUSTUM_DRAW_MODE_FAR_PLANE, FRUSTUM_DRAW_MODE_COUNT }; + FrustumDrawMode _frustumDrawingMode; + + float _viewFrustumOffsetYaw; // the following variables control yaw, pitch, roll and distance form regular + float _viewFrustumOffsetPitch; // camera to the offset camera + float _viewFrustumOffsetRoll; + float _viewFrustumOffsetDistance; + float _viewFrustumOffsetUp; + + float _mouseViewShiftYaw; + float _mouseViewShiftPitch; + + Oscilloscope _audioScope; + + Avatar _myAvatar; // The rendered avatar of oneself + Camera _myCamera; // My view onto the world + Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode + + Environment _environment; + + int _headMouseX, _headMouseY; + + HandControl _handControl; + + int _mouseX; + int _mouseY; + bool _mousePressed; // true if mouse has been pressed (clear when finished) + + // The current mode for mouse interaction + enum MouseMode { NO_EDIT_MODE, ADD_VOXEL_MODE, DELETE_VOXEL_MODE, COLOR_VOXEL_MODE }; + MouseMode _mouseMode; + VoxelDetail _mouseVoxel; // details of the voxel under the mouse cursor + float _mouseVoxelScale; // the scale for adding/removing voxels + + bool _paintOn; // Whether to paint voxels as you fly around + unsigned char _dominantColor; // The dominant color of the voxel we're painting + VoxelDetail _paintingVoxel; // The voxel we're painting if we're painting + + bool _perfStatsOn; // Do we want to display perfStats? + + ChatEntry _chatEntry; // chat entry field + bool _chatEntryOn; // Whether to show the chat entry + + GLuint _oculusTextureID; // The texture to which we render for Oculus distortion + ProgramObject* _oculusProgram; // The GLSL program containing the distortion shader + float _oculusDistortionScale; // Controls the Oculus field of view + int _textureLocation; + int _lensCenterLocation; + int _screenCenterLocation; + int _scaleLocation; + int _scaleInLocation; + int _hmdWarpParamLocation; + + #ifndef _WIN32 + Audio _audio; + #endif + + bool _enableNetworkThread; + pthread_t _networkReceiveThread; + bool _stopNetworkReceiveThread; + + unsigned char _incomingPacket[MAX_PACKET_SIZE]; + int _packetCount; + int _packetsPerSecond; + int _bytesPerSecond; + int _bytesCount; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/SerialInterface.cpp b/interface/src/SerialInterface.cpp index 7aea1a46d9..9fb022471f 100644 --- a/interface/src/SerialInterface.cpp +++ b/interface/src/SerialInterface.cpp @@ -2,16 +2,7 @@ // SerialInterface.cpp // 2012 by Philip Rosedale for High Fidelity Inc. // -// Read interface data from the gyros/accelerometer board using SerialUSB -// -// Channels are received in the following order (integer 0-4096 based on voltage 0-3.3v) -// -// 0 - AIN 15: Pitch Gyro (nodding your head 'yes') -// 1 - AIN 16: Yaw Gyro (shaking your head 'no') -// 2 - AIN 17: Roll Gyro (looking quizzical, tilting your head) -// 3 - AIN 18: Lateral acceleration (moving from side-to-side in front of your monitor) -// 4 - AIN 19: Up/Down acceleration (sitting up/ducking in front of your monitor) -// 5 - AIN 20: Forward/Back acceleration (Toward or away from your monitor) +// Read interface data from the gyros/accelerometer Invensense board using the SerialUSB // #include "SerialInterface.h" @@ -22,14 +13,8 @@ #include #endif -const int MAX_BUFFER = 255; -char serialBuffer[MAX_BUFFER]; -int serialBufferPos = 0; - -const int ZERO_OFFSET = 2048; const short NO_READ_MAXIMUM_MSECS = 3000; -const short SAMPLES_TO_DISCARD = 100; // Throw out the first few samples -const int GRAVITY_SAMPLES = 60; // Use the first samples to compute gravity vector +const int GRAVITY_SAMPLES = 60; // Use the first few samples to baseline values const bool USING_INVENSENSE_MPU9150 = 1; @@ -51,7 +36,7 @@ void SerialInterface::pair() { char *serialPortname = new char[100]; sprintf(serialPortname, "/dev/%s", entry->d_name); - initializePort(serialPortname, 115200); + initializePort(serialPortname); delete [] serialPortname; } @@ -63,7 +48,7 @@ void SerialInterface::pair() { } // connect to the serial port -void SerialInterface::initializePort(char* portname, int baud) { +void SerialInterface::initializePort(char* portname) { #ifdef __APPLE__ _serialDescriptor = open(portname, O_RDWR | O_NOCTTY | O_NDELAY); @@ -72,35 +57,20 @@ void SerialInterface::initializePort(char* portname, int baud) { if (_serialDescriptor == -1) { printLog("Failed.\n"); return; - } - struct termios options; - tcgetattr(_serialDescriptor, &options); - - switch(baud) { - case 9600: cfsetispeed(&options,B9600); - cfsetospeed(&options,B9600); - break; - case 19200: cfsetispeed(&options,B19200); - cfsetospeed(&options,B19200); - break; - case 38400: cfsetispeed(&options,B38400); - cfsetospeed(&options,B38400); - break; - case 115200: cfsetispeed(&options,B115200); - cfsetospeed(&options,B115200); - break; - default:cfsetispeed(&options,B9600); - cfsetospeed(&options,B9600); - break; } - options.c_cflag |= (CLOCAL | CREAD); + struct termios options; + tcgetattr(_serialDescriptor, &options); + + options.c_cflag |= (CLOCAL | CREAD | CS8); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; - options.c_cflag |= CS8; tcsetattr(_serialDescriptor, TCSANOW, &options); + cfsetispeed(&options,B115200); + cfsetospeed(&options,B115200); + if (USING_INVENSENSE_MPU9150) { // block on invensense reads until there is data to read int currentFlags = fcntl(_serialDescriptor, F_GETFL); @@ -140,11 +110,11 @@ void SerialInterface::renderLevels(int width, int height) { const int LEVEL_CORNER_Y = 200; // Draw the numeric degree/sec values from the gyros - sprintf(val, "Yaw %4.1f", _lastYawRate); + sprintf(val, "Yaw %4.1f", getLastYawRate()); drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y, 0.10, 0, 1.0, 1, val, 0, 1, 0); - sprintf(val, "Pitch %4.1f", _lastPitchRate); + sprintf(val, "Pitch %4.1f", getLastPitchRate()); drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 15, 0.10, 0, 1.0, 1, val, 0, 1, 0); - sprintf(val, "Roll %4.1f", _lastRollRate); + sprintf(val, "Roll %4.1f", getLastRollRate()); drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 30, 0.10, 0, 1.0, 1, val, 0, 1, 0); sprintf(val, "X %4.3f", _lastAccelX); drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 45, 0.10, 0, 1.0, 1, val, 0, 1, 0); @@ -161,11 +131,11 @@ void SerialInterface::renderLevels(int width, int height) { glBegin(GL_LINES); // Gyro rates glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y - 3); - glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastYawRate, LEVEL_CORNER_Y - 3); + glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + getLastYawRate(), LEVEL_CORNER_Y - 3); glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 12); - glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastPitchRate, LEVEL_CORNER_Y + 12); + glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + getLastPitchRate(), LEVEL_CORNER_Y + 12); glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 27); - glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastRollRate, LEVEL_CORNER_Y + 27); + glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + getLastRollRate(), LEVEL_CORNER_Y + 27); // Acceleration glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 42); glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + (int)((_lastAccelX - _gravity.x)* ACCEL_VIEW_SCALING), @@ -232,26 +202,33 @@ void SerialInterface::readData() { convertHexToInt(sensorBuffer + 30, pitchRate); // Convert the integer rates to floats - const float LSB_TO_DEGREES_PER_SECOND = 1.f / 16.4f; // From MPU-9150 register map, 2000 deg/sec. - const float PITCH_BIAS = 2.0; // Strangely, there is a small DC bias in the - // invensense pitch reading. Gravity? - + const float LSB_TO_DEGREES_PER_SECOND = 1.f / 16.4f; // From MPU-9150 register map, 2000 deg/sec. _lastRollRate = ((float) rollRate) * LSB_TO_DEGREES_PER_SECOND; _lastYawRate = ((float) yawRate) * LSB_TO_DEGREES_PER_SECOND; - _lastPitchRate = ((float) -pitchRate) * LSB_TO_DEGREES_PER_SECOND + PITCH_BIAS; + _lastPitchRate = ((float) -pitchRate) * LSB_TO_DEGREES_PER_SECOND; - // Accumulate an initial reading for gravity - // Use a set of initial samples to compute gravity - if (totalSamples < GRAVITY_SAMPLES) { - _gravity.x += _lastAccelX; - _gravity.y += _lastAccelY; - _gravity.z += _lastAccelZ; - } - if (totalSamples == GRAVITY_SAMPLES) { - _gravity /= (float) totalSamples; - printLog("Gravity: %f\n", glm::length(_gravity)); - } + // Accumulate a set of initial baseline readings for setting gravity + if (totalSamples == 0) { + _averageGyroRates[0] = _lastRollRate; + _averageGyroRates[1] = _lastYawRate; + _averageGyroRates[2] = _lastPitchRate; + _gravity.x = _lastAccelX; + _gravity.y = _lastAccelY; + _gravity.z = _lastAccelZ; + } + else if (totalSamples < GRAVITY_SAMPLES) { + _gravity = (1.f - 1.f/(float)GRAVITY_SAMPLES) * _gravity + + 1.f/(float)GRAVITY_SAMPLES * glm::vec3(_lastAccelX, _lastAccelY, _lastAccelZ); + + _averageGyroRates[0] = (1.f - 1.f/(float)GRAVITY_SAMPLES) * _averageGyroRates[0] + + 1.f/(float)GRAVITY_SAMPLES * _lastRollRate; + _averageGyroRates[1] = (1.f - 1.f/(float)GRAVITY_SAMPLES) * _averageGyroRates[1] + + 1.f/(float)GRAVITY_SAMPLES * _lastYawRate; + _averageGyroRates[2] = (1.f - 1.f/(float)GRAVITY_SAMPLES) * _averageGyroRates[2] + + 1.f/(float)GRAVITY_SAMPLES * _lastPitchRate; + } + totalSamples++; } @@ -269,14 +246,17 @@ void SerialInterface::readData() { #endif } -void SerialInterface::resetSerial() { -#ifdef __APPLE__ - active = false; +void SerialInterface::resetAverages() { totalSamples = 0; _gravity = glm::vec3(0, 0, 0); - + _averageGyroRates = glm::vec3(0, 0, 0); +} + +void SerialInterface::resetSerial() { +#ifdef __APPLE__ + resetAverages(); + active = false; gettimeofday(&lastGoodRead, NULL); - #endif } diff --git a/interface/src/SerialInterface.h b/interface/src/SerialInterface.h index 65a802d0c2..9aa7bccf04 100644 --- a/interface/src/SerialInterface.h +++ b/interface/src/SerialInterface.h @@ -37,6 +37,8 @@ extern const bool USING_INVENSENSE_MPU9150; class SerialInterface { public: SerialInterface() : active(false), + _gravity(0,0,0), + _averageGyroRates(0,0,0), _lastAccelX(0), _lastAccelY(0), _lastAccelZ(0), @@ -47,23 +49,25 @@ public: void pair(); void readData(); - float getLastYawRate() const { return _lastYawRate; } - float getLastPitchRate() const { return _lastPitchRate; } - float getLastRollRate() const { return _lastRollRate; } + float getLastYawRate() const { return _lastYawRate - _averageGyroRates[1]; } + float getLastPitchRate() const { return _lastPitchRate - _averageGyroRates[2]; } + float getLastRollRate() const { return _lastRollRate - _averageGyroRates[0]; } glm::vec3 getLastAcceleration() { return glm::vec3(_lastAccelX, _lastAccelY, _lastAccelZ); }; glm::vec3 getGravity() {return _gravity;}; void renderLevels(int width, int height); + void resetAverages(); bool active; private: - void initializePort(char* portname, int baud); + void initializePort(char* portname); void resetSerial(); int _serialDescriptor; int totalSamples; timeval lastGoodRead; glm::vec3 _gravity; + glm::vec3 _averageGyroRates; float _lastAccelX; float _lastAccelY; float _lastAccelZ; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 7ea9344f3b..669e64abc1 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -15,2185 +15,14 @@ // Welcome Aboard! // -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include "Syssocket.h" -#include "Systime.h" -#else -#include -#include -#include -#endif - -#include - -#include -#include -#include - -#include "InterfaceConfig.h" - -#include "Log.h" -#include "shared_Log.h" -#include "voxels_Log.h" -#include "avatars_Log.h" - -#include "world.h" -#include "Util.h" -#ifndef _WIN32 -#include "Audio.h" -#endif - -#include "AngleUtil.h" -#include "Stars.h" - -#include "ui/ChatEntry.h" -#include "ui/MenuRow.h" -#include "ui/MenuColumn.h" -#include "ui/Menu.h" -#include "ui/TextRenderer.h" -#include "renderer/ProgramObject.h" -#include "renderer/ShaderObject.h" - #include "Application.h" -#include "Camera.h" -#include "Avatar.h" -#include -#include -#include "VoxelSystem.h" -#include "Environment.h" -#include "Oscilloscope.h" -#include "UDPSocket.h" -#include "SerialInterface.h" -#include -#include -#include -#include - -#include "ViewFrustum.h" -#include "HandControl.h" -#include "AvatarRenderer.h" -#include "OculusManager.h" - -using namespace std; - -void reshape(int width, int height); // will be defined below -void loadViewFrustum(ViewFrustum& viewFrustum); // will be defined below - -glm::vec3 getGravity(glm::vec3 pos); //get the local gravity vector at this location in the universe - -bool enableNetworkThread = true; -pthread_t networkReceiveThread; -bool stopNetworkReceiveThread = false; - -unsigned char incomingPacket[MAX_PACKET_SIZE]; -int packetCount = 0; -int packetsPerSecond = 0; -int bytesPerSecond = 0; -int bytesCount = 0; - - -int screenWidth = 1200; // Window size -int screenHeight = 800; - -int fullscreen = 0; -float aspectRatio = 1.0f; - -// PER: Jeffrey - please move these our of main.cpp - also these not constants! -float mouseViewShiftYaw = 0.0f; -float mouseViewShiftPitch = 0.0f; -bool USING_MOUSE_VIEW_SHIFT = false; -float MOUSE_VIEW_SHIFT_RATE = 40.0f; -float MOUSE_VIEW_SHIFT_YAW_MARGIN = (float)(::screenWidth * 0.2f); -float MOUSE_VIEW_SHIFT_PITCH_MARGIN = (float)(::screenHeight * 0.2f); -float MOUSE_VIEW_SHIFT_YAW_LIMIT = 45.0; -float MOUSE_VIEW_SHIFT_PITCH_LIMIT = 30.0; - -bool wantColorRandomizer = true; // for addSphere and load file - -Oscilloscope audioScope(256,200,true); - -ViewFrustum viewFrustum; // current state of view frustum, perspective, orientation, etc. - -Avatar myAvatar(true); // The rendered avatar of oneself -Camera myCamera; // My view onto the world -Camera viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode - -AvatarRenderer avatarRenderer; - -// Starfield information -char starFile[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; -char starCacheFile[] = "cachedStars.txt"; -Stars stars; - -glm::vec3 box(WORLD_SIZE,WORLD_SIZE,WORLD_SIZE); - -VoxelSystem voxels; - -Environment environment; - - -#ifndef _WIN32 -Audio audio(&audioScope, &myAvatar); -#endif - -#define IDLE_SIMULATE_MSECS 16 // How often should call simulate and other stuff - // in the idle loop? (60 FPS is default) - - -glm::vec3 start_location(6.1f, 0, 1.4f); // Where one's own agent begins in the world - // (will be overwritten if avatar data file is found) - -bool renderWarningsOn = false; // Whether to show render pipeline warnings -bool renderStatsOn = false; // Whether to show onscreen text overlay with stats -bool renderVoxels = true; // Whether to render voxels -bool renderStarsOn = true; // Whether to display the stars -bool renderAtmosphereOn = true; // Whether to display the atmosphere -bool renderAvatarsOn = true; // Whether to render avatars -bool renderFirstPersonOn = false; // Whether to render in first person -bool paintOn = false; // Whether to paint voxels as you fly around -VoxelDetail paintingVoxel; // The voxel we're painting if we're painting -unsigned char dominantColor = 0; // The dominant color of the voxel we're painting -bool perfStatsOn = false; // Do we want to display perfStats? -bool wantMonochrome = false; // ask server to send us in monochrome -bool wantResIn = false; // ask server to res in -bool wantDelta = false; // ask server to send delta only - -bool logOn = true; // Whether to show on-screen log - -bool wantToKillLocalVoxels = false; - -int noiseOn = 0; // Whether to add random noise -float noise = 1.0; // Overall magnitude scaling for random noise levels - -bool gyroLook = true; // Whether to allow the gyro data from head to move your view - -int displayLevels = 0; -bool lookingInMirror = 0; // Are we currently rendering one's own head as if in mirror? - -int displayHeadMouse = 1; // Display sample mouse pointer controlled by head movement -int headMouseX, headMouseY; - -HandControl handControl; - -int mouseX = 0; -int mouseY = 0; - -// Mouse location at start of last down click -int mousePressed = 0; // true if mouse has been pressed (clear when finished) - -// The current mode for mouse interaction -enum MouseMode { NO_EDIT_MODE, ADD_VOXEL_MODE, DELETE_VOXEL_MODE, COLOR_VOXEL_MODE }; -MouseMode mouseMode = NO_EDIT_MODE; -VoxelDetail mouseVoxel; // details of the voxel under the mouse cursor -float mouseVoxelScale = 1.0f / 1024.0f; // the scale for adding/removing voxels - -Menu menu; // main menu -int menuOn = 1; // Whether to show onscreen menu - -ChatEntry chatEntry; // chat entry field -bool chatEntryOn = false; // Whether to show the chat entry - -bool oculusOn = false; // Whether to configure the display for the Oculus Rift -GLuint oculusTextureID = 0; // The texture to which we render for Oculus distortion -ProgramObject* oculusProgram = 0; // The GLSL program containing the distortion shader -float oculusDistortionScale = 1.25; // Controls the Oculus field of viewa - -SerialInterface serialPort; - -glm::vec3 gravity; - -// Frame Rate Measurement - -int frameCount = 0; -float FPS = 120.f; -timeval timerStart, timerEnd; -timeval lastTimeIdle; -double elapsedTime; -timeval applicationStartupTime; -bool justStarted = true; - -// Every second, check the frame rates and other stuff -void Timer(int extra) { - gettimeofday(&timerEnd, NULL); - FPS = (float)frameCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f); - packetsPerSecond = (float)packetCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f); - bytesPerSecond = (float)bytesCount / ((float)diffclock(&timerStart, &timerEnd) / 1000.f); - frameCount = 0; - packetCount = 0; - bytesCount = 0; - - glutTimerFunc(1000,Timer,0); - gettimeofday(&timerStart, NULL); - - // if we haven't detected gyros, check for them now - if (!serialPort.active) { - serialPort.pair(); - } -} - -void displayStats(void) { - int statsVerticalOffset = 50; - if (::menuOn == 0) { - statsVerticalOffset = 8; - } - - char stats[200]; - sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps", - FPS, packetsPerSecond, (float)bytesPerSecond * 8.f / 1000000.f); - drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, stats); - - std::stringstream voxelStats; - voxelStats.precision(4); - voxelStats << "Voxels Rendered: " << voxels.getVoxelsRendered() / 1000.f << "K Updated: " << voxels.getVoxelsUpdated()/1000.f << "K"; - drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - voxelStats << "Voxels Created: " << voxels.getVoxelsCreated() / 1000.f << "K (" << voxels.getVoxelsCreatedPerSecondAverage() / 1000.f - << "Kps) "; - drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - voxelStats << "Voxels Colored: " << voxels.getVoxelsColored() / 1000.f << "K (" << voxels.getVoxelsColoredPerSecondAverage() / 1000.f - << "Kps) "; - drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - voxelStats << "Voxel Bits Read: " << voxels.getVoxelsBytesRead() * 8.f / 1000000.f - << "M (" << voxels.getVoxelsBytesReadPerSecondAverage() * 8.f / 1000000.f << " Mbps)"; - drawtext(10, statsVerticalOffset + 290,0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - float voxelsBytesPerColored = voxels.getVoxelsColored() - ? ((float) voxels.getVoxelsBytesRead() / voxels.getVoxelsColored()) - : 0; - - voxelStats << "Voxels Bits per Colored: " << voxelsBytesPerColored * 8; - drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - Agent *avatarMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AVATAR_MIXER); - char avatarMixerStats[200]; - - if (avatarMixer) { - sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", - roundf(avatarMixer->getAverageKilobitsPerSecond()), - roundf(avatarMixer->getAveragePacketsPerSecond())); - } else { - sprintf(avatarMixerStats, "No Avatar Mixer"); - } - - drawtext(10, statsVerticalOffset + 330, 0.10f, 0, 1.0, 0, avatarMixerStats); - - if (::perfStatsOn) { - // Get the PerfStats group details. We need to allocate and array of char* long enough to hold 1+groups - char** perfStatLinesArray = new char*[PerfStat::getGroupCount()+1]; - int lines = PerfStat::DumpStats(perfStatLinesArray); - int atZ = 150; // arbitrary place on screen that looks good - for (int line=0; line < lines; line++) { - drawtext(10, statsVerticalOffset + atZ, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]); - delete perfStatLinesArray[line]; // we're responsible for cleanup - perfStatLinesArray[line]=NULL; - atZ+=20; // height of a line - } - delete []perfStatLinesArray; // we're responsible for cleanup - } -} - -void initDisplay(void) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glShadeModel (GL_SMOOTH); - glEnable(GL_LIGHTING); - glEnable(GL_LIGHT0); - glEnable(GL_DEPTH_TEST); - - if (fullscreen) glutFullScreen(); -} - -void init(void) { - voxels.init(); - voxels.setViewerAvatar(&myAvatar); - voxels.setCamera(&myCamera); - - environment.init(); - - handControl.setScreenDimensions(::screenWidth, ::screenHeight); - - headMouseX = ::screenWidth /2; - headMouseY = ::screenHeight/2; - - stars.readInput(starFile, starCacheFile, 0); - - if (noiseOn) { - myAvatar.setNoise(noise); - } - - myAvatar.setPosition(start_location); - Camera::CameraFollowingAttributes a; - a.upShift = -0.2f; - a.distance = 1.5f; - a.tightness = 8.0f; - myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); - myAvatar.setDisplayingHead(true); - - OculusManager::connect(); - - gettimeofday(&timerStart, NULL); - gettimeofday(&lastTimeIdle, NULL); -} - -void terminate () { - // Close serial port - // close(serial_fd); - - myAvatar.writeAvatarDataToFile(); - - #ifndef _WIN32 - audio.terminate(); - #endif - - if (enableNetworkThread) { - stopNetworkReceiveThread = true; - pthread_join(networkReceiveThread, NULL); - } - - exit(EXIT_SUCCESS); -} - -void reset_sensors() { - - myAvatar.setPosition(start_location); - headMouseX = ::screenWidth / 2; - headMouseY = ::screenHeight / 2; - - myAvatar.reset(); -} - -void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { - unsigned char* bufferOut; - int sizeOut; - - if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ - AgentList::getInstance()->broadcastToAgents(bufferOut, sizeOut, &AGENT_TYPE_VOXEL, 1); - delete bufferOut; - } -} - -// Using gyro data, update both view frustum and avatar head position -void updateAvatar(float deltaTime) { - - // Update my avatar's head position from gyros - myAvatar.updateHeadFromGyros(deltaTime, &serialPort, &gravity); - - // Grab latest readings from the gyros - float measuredPitchRate = serialPort.getLastPitchRate(); - float measuredYawRate = serialPort.getLastYawRate(); - - // Update gyro-based mouse (X,Y on screen) - const float MIN_MOUSE_RATE = 1.0; - const float HORIZONTAL_PIXELS_PER_DEGREE = 2880.f / 45.f; - const float VERTICAL_PIXELS_PER_DEGREE = 1800.f / 30.f; - if (powf(measuredYawRate * measuredYawRate + - measuredPitchRate * measuredPitchRate, 0.5) > MIN_MOUSE_RATE) - { - headMouseX += measuredYawRate * HORIZONTAL_PIXELS_PER_DEGREE * deltaTime; - headMouseY -= measuredPitchRate * VERTICAL_PIXELS_PER_DEGREE * deltaTime; - } - headMouseX = max(headMouseX, 0); - headMouseX = min(headMouseX, ::screenWidth); - headMouseY = max(headMouseY, 0); - headMouseY = min(headMouseY, ::screenHeight); - - // Update head and body pitch and yaw based on measured gyro rates - if (::gyroLook) { - // Render Yaw - float renderYawSpring = fabs(headMouseX - ::screenWidth / 2.f) / (::screenWidth / 2.f); - const float RENDER_YAW_MULTIPLY = 4.f; - myAvatar.setRenderYaw((1.f - renderYawSpring * deltaTime) * myAvatar.getRenderYaw() + - renderYawSpring * deltaTime * -myAvatar.getHeadYaw() * RENDER_YAW_MULTIPLY); - // Render Pitch - float renderPitchSpring = fabs(headMouseY - ::screenHeight / 2.f) / (::screenHeight / 2.f); - const float RENDER_PITCH_MULTIPLY = 4.f; - myAvatar.setRenderPitch((1.f - renderPitchSpring * deltaTime) * myAvatar.getRenderPitch() + - renderPitchSpring * deltaTime * -myAvatar.getHeadPitch() * RENDER_PITCH_MULTIPLY); - } - - - if (USING_MOUSE_VIEW_SHIFT) - { - //make it so that when your mouse hits the edge of the screen, the camera shifts - float rightBoundary = (float)::screenWidth - MOUSE_VIEW_SHIFT_YAW_MARGIN; - float bottomBoundary = (float)::screenHeight - MOUSE_VIEW_SHIFT_PITCH_MARGIN; - - if (mouseX > rightBoundary) { - float f = (mouseX - rightBoundary) / ( (float)::screenWidth - rightBoundary); - mouseViewShiftYaw += MOUSE_VIEW_SHIFT_RATE * f * deltaTime; - if (mouseViewShiftYaw > MOUSE_VIEW_SHIFT_YAW_LIMIT) { mouseViewShiftYaw = MOUSE_VIEW_SHIFT_YAW_LIMIT; } - } else if (mouseX < MOUSE_VIEW_SHIFT_YAW_MARGIN) { - float f = 1.0 - (mouseX / MOUSE_VIEW_SHIFT_YAW_MARGIN); - mouseViewShiftYaw -= MOUSE_VIEW_SHIFT_RATE * f * deltaTime; - if (mouseViewShiftYaw < -MOUSE_VIEW_SHIFT_YAW_LIMIT) { mouseViewShiftYaw = -MOUSE_VIEW_SHIFT_YAW_LIMIT; } - } - if (mouseY < MOUSE_VIEW_SHIFT_PITCH_MARGIN) { - float f = 1.0 - (mouseY / MOUSE_VIEW_SHIFT_PITCH_MARGIN); - mouseViewShiftPitch += MOUSE_VIEW_SHIFT_RATE * f * deltaTime; - if ( mouseViewShiftPitch > MOUSE_VIEW_SHIFT_PITCH_LIMIT ) { mouseViewShiftPitch = MOUSE_VIEW_SHIFT_PITCH_LIMIT; } - } - else if (mouseY > bottomBoundary) { - float f = (mouseY - bottomBoundary) / ((float)::screenHeight - bottomBoundary); - mouseViewShiftPitch -= MOUSE_VIEW_SHIFT_RATE * f * deltaTime; - if (mouseViewShiftPitch < -MOUSE_VIEW_SHIFT_PITCH_LIMIT) { mouseViewShiftPitch = -MOUSE_VIEW_SHIFT_PITCH_LIMIT; } - } - } - - if (OculusManager::isConnected()) { - float yaw, pitch, roll; - OculusManager::getEulerAngles(yaw, pitch, roll); - - myAvatar.setHeadYaw(-yaw); - myAvatar.setHeadPitch(pitch); - myAvatar.setHeadRoll(roll); - } - - // Get audio loudness data from audio input device - #ifndef _WIN32 - myAvatar.setLoudness(audio.getInputLoudness()); - #endif - - // Update Avatar with latest camera and view frustum data... - // NOTE: we get this from the view frustum, to make it simpler, since the - // loadViewFrumstum() method will get the correct details from the camera - // We could optimize this to not actually load the viewFrustum, since we don't - // actually need to calculate the view frustum planes to send these details - // to the server. - loadViewFrustum(::viewFrustum); - myAvatar.setCameraPosition(::viewFrustum.getPosition()); - myAvatar.setCameraDirection(::viewFrustum.getDirection()); - myAvatar.setCameraUp(::viewFrustum.getUp()); - myAvatar.setCameraRight(::viewFrustum.getRight()); - myAvatar.setCameraFov(::viewFrustum.getFieldOfView()); - myAvatar.setCameraAspectRatio(::viewFrustum.getAspectRatio()); - myAvatar.setCameraNearClip(::viewFrustum.getNearClip()); - myAvatar.setCameraFarClip(::viewFrustum.getFarClip()); - - AgentList* agentList = AgentList::getInstance(); - if (agentList->getOwnerID() != UNKNOWN_AGENT_ID) { - // if I know my ID, send head/hand data to the avatar mixer and voxel server - unsigned char broadcastString[200]; - unsigned char* endOfBroadcastStringWrite = broadcastString; - - *(endOfBroadcastStringWrite++) = PACKET_HEADER_HEAD_DATA; - endOfBroadcastStringWrite += packAgentId(endOfBroadcastStringWrite, agentList->getOwnerID()); - - endOfBroadcastStringWrite += myAvatar.getBroadcastData(endOfBroadcastStringWrite); - - const char broadcastReceivers[2] = {AGENT_TYPE_VOXEL, AGENT_TYPE_AVATAR_MIXER}; - AgentList::getInstance()->broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, broadcastReceivers, sizeof(broadcastReceivers)); - } - - // If I'm in paint mode, send a voxel out to VOXEL server agents. - if (::paintOn) { - - glm::vec3 avatarPos = myAvatar.getPosition(); - - // For some reason, we don't want to flip X and Z here. - ::paintingVoxel.x = avatarPos.x / 10.0; - ::paintingVoxel.y = avatarPos.y / 10.0; - ::paintingVoxel.z = avatarPos.z / 10.0; - - if (::paintingVoxel.x >= 0.0 && ::paintingVoxel.x <= 1.0 && - ::paintingVoxel.y >= 0.0 && ::paintingVoxel.y <= 1.0 && - ::paintingVoxel.z >= 0.0 && ::paintingVoxel.z <= 1.0) { - - sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, ::paintingVoxel); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////// -// loadViewFrustum() -// -// Description: this will load the view frustum bounds for EITHER the head -// or the "myCamera". -// - -// These global scoped variables are used by our loadViewFrustum() and renderViewFrustum functions below, but are also -// available as globals so that the keyboard and menu can manipulate them. - -bool frustumOn = false; // Whether or not to display the debug view frustum -bool cameraFrustum = true; // which frustum to look at - -bool viewFrustumFromOffset =false; // Wether or not to offset the view of the frustum -float viewFrustumOffsetYaw = -135.0; // the following variables control yaw, pitch, roll and distance form regular -float viewFrustumOffsetPitch = 0.0; // camera to the offset camera -float viewFrustumOffsetRoll = 0.0; -float viewFrustumOffsetDistance = 25.0; -float viewFrustumOffsetUp = 0.0; - -void loadViewFrustum(ViewFrustum& viewFrustum) { - // We will use these below, from either the camera or head vectors calculated above - glm::vec3 position; - glm::vec3 direction; - glm::vec3 up; - glm::vec3 right; - float fov, nearClip, farClip; - - // Camera or Head? - if (::cameraFrustum) { - position = ::myCamera.getPosition(); - } else { - position = ::myAvatar.getHeadPosition(); - } - - fov = ::myCamera.getFieldOfView(); - nearClip = ::myCamera.getNearClip(); - farClip = ::myCamera.getFarClip(); - - Orientation o = ::myCamera.getOrientation(); - - direction = o.getFront(); - up = o.getUp(); - right = o.getRight(); - - /* - printf("position.x=%f, position.y=%f, position.z=%f\n", position.x, position.y, position.z); - printf("yaw=%f, pitch=%f, roll=%f\n", yaw,pitch,roll); - printf("direction.x=%f, direction.y=%f, direction.z=%f\n", direction.x, direction.y, direction.z); - printf("up.x=%f, up.y=%f, up.z=%f\n", up.x, up.y, up.z); - printf("right.x=%f, right.y=%f, right.z=%f\n", right.x, right.y, right.z); - printf("fov=%f\n", fov); - printf("nearClip=%f\n", nearClip); - printf("farClip=%f\n", farClip); - */ - - // Set the viewFrustum up with the correct position and orientation of the camera - viewFrustum.setPosition(position); - viewFrustum.setOrientation(direction,up,right); - - // Also make sure it's got the correct lens details from the camera - viewFrustum.setFieldOfView(fov); - viewFrustum.setNearClip(nearClip); - viewFrustum.setFarClip(farClip); - - // Ask the ViewFrustum class to calculate our corners - viewFrustum.calculate(); -} - -///////////////////////////////////////////////////////////////////////////////////// -// renderViewFrustum() -// -// Description: this will render the view frustum bounds for EITHER the head -// or the "myCamera". -// -// Frustum rendering mode. For debug purposes, we allow drawing the frustum in a couple of different ways. -// We can draw it with each of these parts: -// * Origin Direction/Up/Right vectors - these will be drawn at the point of the camera -// * Near plane - this plane is drawn very close to the origin point. -// * Right/Left planes - these two planes are drawn between the near and far planes. -// * Far plane - the plane is drawn in the distance. -// Modes - the following modes, will draw the following parts. -// * All - draws all the parts listed above -// * Planes - draws the planes but not the origin vectors -// * Origin Vectors - draws the origin vectors ONLY -// * Near Plane - draws only the near plane -// * Far Plane - draws only the far plane -#define FRUSTUM_DRAW_MODE_ALL 0 -#define FRUSTUM_DRAW_MODE_VECTORS 1 -#define FRUSTUM_DRAW_MODE_PLANES 2 -#define FRUSTUM_DRAW_MODE_NEAR_PLANE 3 -#define FRUSTUM_DRAW_MODE_FAR_PLANE 4 -#define FRUSTUM_DRAW_MODE_COUNT 5 - -int frustumDrawingMode = FRUSTUM_DRAW_MODE_ALL; // the mode we're drawing the frustum in, see notes above - -void renderViewFrustum(ViewFrustum& viewFrustum) { - - // Load it with the latest details! - loadViewFrustum(viewFrustum); - - glm::vec3 position = viewFrustum.getPosition(); - glm::vec3 direction = viewFrustum.getDirection(); - glm::vec3 up = viewFrustum.getUp(); - glm::vec3 right = viewFrustum.getRight(); - - // Get ready to draw some lines - glDisable(GL_LIGHTING); - glColor4f(1.0, 1.0, 1.0, 1.0); - glLineWidth(1.0); - glBegin(GL_LINES); - - if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_VECTORS) { - // Calculate the origin direction vectors - glm::vec3 lookingAt = position + (direction * 0.2f); - glm::vec3 lookingAtUp = position + (up * 0.2f); - glm::vec3 lookingAtRight = position + (right * 0.2f); - - // Looking At = white - glColor3f(1,1,1); - glVertex3f(position.x, position.y, position.z); - glVertex3f(lookingAt.x, lookingAt.y, lookingAt.z); - - // Looking At Up = purple - glColor3f(1,0,1); - glVertex3f(position.x, position.y, position.z); - glVertex3f(lookingAtUp.x, lookingAtUp.y, lookingAtUp.z); - - // Looking At Right = cyan - glColor3f(0,1,1); - glVertex3f(position.x, position.y, position.z); - glVertex3f(lookingAtRight.x, lookingAtRight.y, lookingAtRight.z); - } - - if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES - || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_NEAR_PLANE) { - // Drawing the bounds of the frustum - // viewFrustum.getNear plane - bottom edge - glColor3f(1,0,0); - glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); - glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); - - // viewFrustum.getNear plane - top edge - glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); - glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); - - // viewFrustum.getNear plane - right edge - glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); - glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); - - // viewFrustum.getNear plane - left edge - glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); - glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); - } - - if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES - || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_FAR_PLANE) { - // viewFrustum.getFar plane - bottom edge - glColor3f(0,1,0); // GREEN!!! - glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); - glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); - - // viewFrustum.getFar plane - top edge - glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); - glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); - - // viewFrustum.getFar plane - right edge - glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); - glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); - - // viewFrustum.getFar plane - left edge - glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); - glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); - } - - if (::frustumDrawingMode == FRUSTUM_DRAW_MODE_ALL || ::frustumDrawingMode == FRUSTUM_DRAW_MODE_PLANES) { - // RIGHT PLANE IS CYAN - // right plane - bottom edge - viewFrustum.getNear to distant - glColor3f(0,1,1); - glVertex3f(viewFrustum.getNearBottomRight().x, viewFrustum.getNearBottomRight().y, viewFrustum.getNearBottomRight().z); - glVertex3f(viewFrustum.getFarBottomRight().x, viewFrustum.getFarBottomRight().y, viewFrustum.getFarBottomRight().z); - - // right plane - top edge - viewFrustum.getNear to distant - glVertex3f(viewFrustum.getNearTopRight().x, viewFrustum.getNearTopRight().y, viewFrustum.getNearTopRight().z); - glVertex3f(viewFrustum.getFarTopRight().x, viewFrustum.getFarTopRight().y, viewFrustum.getFarTopRight().z); - - // LEFT PLANE IS BLUE - // left plane - bottom edge - viewFrustum.getNear to distant - glColor3f(0,0,1); - glVertex3f(viewFrustum.getNearBottomLeft().x, viewFrustum.getNearBottomLeft().y, viewFrustum.getNearBottomLeft().z); - glVertex3f(viewFrustum.getFarBottomLeft().x, viewFrustum.getFarBottomLeft().y, viewFrustum.getFarBottomLeft().z); - - // left plane - top edge - viewFrustum.getNear to distant - glVertex3f(viewFrustum.getNearTopLeft().x, viewFrustum.getNearTopLeft().y, viewFrustum.getNearTopLeft().z); - glVertex3f(viewFrustum.getFarTopLeft().x, viewFrustum.getFarTopLeft().y, viewFrustum.getFarTopLeft().z); - } - - glEnd(); - glEnable(GL_LIGHTING); -} - -// displays a single side (left, right, or combined for non-Oculus) -void displaySide(Camera& whichCamera) { - glPushMatrix(); - - if (::renderStarsOn) { - // should be the first rendering pass - w/o depth buffer / lighting - - // compute starfield alpha based on distance from atmosphere - float alpha = 1.0f; - if (::renderAtmosphereOn) { - float height = glm::distance(whichCamera.getPosition(), environment.getAtmosphereCenter()); - if (height < environment.getAtmosphereInnerRadius()) { - alpha = 0.0f; - - } else if (height < environment.getAtmosphereOuterRadius()) { - alpha = (height - environment.getAtmosphereInnerRadius()) / - (environment.getAtmosphereOuterRadius() - environment.getAtmosphereInnerRadius()); - } - } - - // finally render the starfield - stars.render(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), whichCamera.getNearClip(), alpha); - } - - // draw the sky dome - if (::renderAtmosphereOn) { - environment.renderAtmosphere(whichCamera); - } - - glEnable(GL_LIGHTING); - glEnable(GL_DEPTH_TEST); - - // draw a red sphere - float sphereRadius = 0.25f; - glColor3f(1,0,0); - glPushMatrix(); - glutSolidSphere(sphereRadius, 15, 15); - glPopMatrix(); - - //draw a grid ground plane.... - drawGroundPlaneGrid(10.f); - - // Draw voxels - if (renderVoxels) { - voxels.render(); - } - - // indicate what we'll be adding/removing in mouse mode, if anything - if (::mouseVoxel.s != 0) { - glPushMatrix(); - if (::mouseMode == ADD_VOXEL_MODE) { - // use a contrasting color so that we can see what we're doing - glColor3ub(::mouseVoxel.red + 128, ::mouseVoxel.green + 128, ::mouseVoxel.blue + 128); - } else { - glColor3ub(::mouseVoxel.red, ::mouseVoxel.green, ::mouseVoxel.blue); - } - glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); - glTranslatef(::mouseVoxel.x + ::mouseVoxel.s*0.5f, - ::mouseVoxel.y + ::mouseVoxel.s*0.5f, - ::mouseVoxel.z + ::mouseVoxel.s*0.5f); - glLineWidth(4.0f); - glutWireCube(::mouseVoxel.s); - glLineWidth(1.0f); - glPopMatrix(); - } - - if (::renderAvatarsOn) { - // Render avatars of other agents - AgentList* agentList = AgentList::getInstance(); - agentList->lock(); - for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { - if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) { - Avatar *avatar = (Avatar *)agent->getLinkedData(); - avatar->render(0, ::myCamera.getPosition()); - } - } - agentList->unlock(); - - // Render my own Avatar - myAvatar.render(::lookingInMirror, ::myCamera.getPosition()); - } - - // Render the world box - if (!::lookingInMirror && ::renderStatsOn) { render_world_box(); } - - // brad's frustum for debugging - if (::frustumOn) renderViewFrustum(::viewFrustum); - - glPopMatrix(); -} - -// this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample -// code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license -// (http://www.apache.org/licenses/LICENSE-2.0) -const char* DISTORTION_FRAGMENT_SHADER = - "#version 120\n" - "uniform sampler2D texture;" - "uniform vec2 lensCenter;" - "uniform vec2 screenCenter;" - "uniform vec2 scale;" - "uniform vec2 scaleIn;" - "uniform vec4 hmdWarpParam;" - "vec2 hmdWarp(vec2 in01) {" - " vec2 theta = (in01 - lensCenter) * scaleIn;" - " float rSq = theta.x * theta.x + theta.y * theta.y;" - " vec2 theta1 = theta * (hmdWarpParam.x + hmdWarpParam.y * rSq + " - " hmdWarpParam.z * rSq * rSq + hmdWarpParam.w * rSq * rSq * rSq);" - " return lensCenter + scale * theta1;" - "}" - "void main(void) {" - " vec2 tc = hmdWarp(gl_TexCoord[0].st);" - " vec2 below = step(screenCenter.st + vec2(-0.25, -0.5), tc.st);" - " vec2 above = vec2(1.0, 1.0) - step(screenCenter.st + vec2(0.25, 0.5), tc.st);" - " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texture2D(texture, tc), " - " above.s * above.t * below.s * below.t);" - "}"; - -// the locations of the uniform variables -int textureLocation; -int lensCenterLocation; -int screenCenterLocation; -int scaleLocation; -int scaleInLocation; -int hmdWarpParamLocation; - -// renders both sides into a texture, then renders the texture to the display with distortion -void displayOculus(Camera& whichCamera) { - // magic numbers ahoy! in order to avoid pulling in the Oculus utility library that calculates - // the rendering parameters from the hardware stats, i just folded their calculations into - // constants using the stats for the current-model hardware as contained in the SDK file - // LibOVR/Src/Util/Util_Render_Stereo.cpp - - // eye - - // render the left eye view to the left side of the screen - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glTranslatef(0.151976, 0, 0); // +h, see Oculus SDK docs p. 26 - gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), - whichCamera.getNearClip(), whichCamera.getFarClip()); - glTranslatef(0.032, 0, 0); // dip/2, see p. 27 - - glMatrixMode(GL_MODELVIEW); - glViewport(0, 0, ::screenWidth / 2, ::screenHeight); - displaySide(whichCamera); - - // and the right eye to the right side - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glTranslatef(-0.151976, 0, 0); // -h - gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(), - whichCamera.getNearClip(), whichCamera.getFarClip()); - glTranslatef(-0.032, 0, 0); - - glMatrixMode(GL_MODELVIEW); - glViewport(::screenWidth / 2, 0, ::screenWidth / 2, ::screenHeight); - displaySide(whichCamera); - - glPopMatrix(); - - // restore our normal viewport - glViewport(0, 0, ::screenWidth, ::screenHeight); - - if (::oculusTextureID == 0) { - glGenTextures(1, &::oculusTextureID); - glBindTexture(GL_TEXTURE_2D, ::oculusTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ::screenWidth, ::screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - ::oculusProgram = new ProgramObject(); - ::oculusProgram->attachFromSourceCode(GL_FRAGMENT_SHADER_ARB, DISTORTION_FRAGMENT_SHADER); - ::oculusProgram->link(); - - textureLocation = ::oculusProgram->getUniformLocation("texture"); - lensCenterLocation = ::oculusProgram->getUniformLocation("lensCenter"); - screenCenterLocation = ::oculusProgram->getUniformLocation("screenCenter"); - scaleLocation = ::oculusProgram->getUniformLocation("scale"); - scaleInLocation = ::oculusProgram->getUniformLocation("scaleIn"); - hmdWarpParamLocation = ::oculusProgram->getUniformLocation("hmdWarpParam"); - - } else { - glBindTexture(GL_TEXTURE_2D, ::oculusTextureID); - } - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, ::screenWidth, ::screenHeight); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluOrtho2D(0, ::screenWidth, 0, ::screenHeight); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - - // for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp - - float scaleFactor = 1.0 / ::oculusDistortionScale; - float aspectRatio = (::screenWidth * 0.5) / ::screenHeight; - - glDisable(GL_BLEND); - glEnable(GL_TEXTURE_2D); - ::oculusProgram->bind(); - ::oculusProgram->setUniform(textureLocation, 0); - ::oculusProgram->setUniform(lensCenterLocation, 0.287994, 0.5); // see SDK docs, p. 29 - ::oculusProgram->setUniform(screenCenterLocation, 0.25, 0.5); - ::oculusProgram->setUniform(scaleLocation, 0.25 * scaleFactor, 0.5 * scaleFactor * aspectRatio); - ::oculusProgram->setUniform(scaleInLocation, 4, 2 / aspectRatio); - ::oculusProgram->setUniform(hmdWarpParamLocation, 1.0, 0.22, 0.24, 0); - - glColor3f(1, 0, 1); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); - glVertex2f(0, 0); - glTexCoord2f(0.5, 0); - glVertex2f(::screenWidth/2, 0); - glTexCoord2f(0.5, 1); - glVertex2f(::screenWidth / 2, ::screenHeight); - glTexCoord2f(0, 1); - glVertex2f(0, ::screenHeight); - glEnd(); - - ::oculusProgram->setUniform(lensCenterLocation, 0.787994, 0.5); - ::oculusProgram->setUniform(screenCenterLocation, 0.75, 0.5); - - glBegin(GL_QUADS); - glTexCoord2f(0.5, 0); - glVertex2f(::screenWidth / 2, 0); - glTexCoord2f(1, 0); - glVertex2f(::screenWidth, 0); - glTexCoord2f(1, 1); - glVertex2f(::screenWidth, ::screenHeight); - glTexCoord2f(0.5, 1); - glVertex2f(::screenWidth / 2, ::screenHeight); - glEnd(); - - glEnable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); - ::oculusProgram->release(); - - glPopMatrix(); -} - -void displayOverlay() { - // Render 2D overlay: I/O level bar graphs and text - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - gluOrtho2D(0, ::screenWidth, ::screenHeight, 0); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - - #ifndef _WIN32 - audio.render(::screenWidth, ::screenHeight); - audioScope.render(20, ::screenHeight - 200); - #endif - - //noiseTest(::screenWidth, ::screenHeight); - - if (displayHeadMouse && !::lookingInMirror && USING_INVENSENSE_MPU9150) { - // Display small target box at center or head mouse target that can also be used to measure LOD - glColor3f(1.0, 1.0, 1.0); - glDisable(GL_LINE_SMOOTH); - const int PIXEL_BOX = 20; - glBegin(GL_LINE_STRIP); - glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY - PIXEL_BOX/2); - glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY - PIXEL_BOX/2); - glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY + PIXEL_BOX/2); - glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY + PIXEL_BOX/2); - glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY - PIXEL_BOX/2); - glEnd(); - glEnable(GL_LINE_SMOOTH); - } - - // Show detected levels from the serial I/O ADC channel sensors - if (displayLevels) serialPort.renderLevels(::screenWidth,::screenHeight); - - // Display stats and log text onscreen - glLineWidth(1.0f); - glPointSize(1.0f); - - if (::renderStatsOn) { displayStats(); } - if (::logOn) { logger.render(::screenWidth, ::screenHeight); } - - // Show menu - if (::menuOn) { - glLineWidth(1.0f); - glPointSize(1.0f); - menu.render(::screenWidth,::screenHeight); - } - - // Show chat entry field - if (::chatEntryOn) { - chatEntry.render(::screenWidth, ::screenHeight); - } - - // Stats at upper right of screen about who domain server is telling us about - glPointSize(1.0f); - char agents[100]; - - AgentList* agentList = AgentList::getInstance(); - int totalAvatars = 0, totalServers = 0; - - for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { - agent->getType() == AGENT_TYPE_AVATAR ? totalAvatars++ : totalServers++; - } - - sprintf(agents, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars); - drawtext(::screenWidth - 150, 20, 0.10, 0, 1.0, 0, agents, 1, 0, 0); - - if (::paintOn) { - - char paintMessage[100]; - sprintf(paintMessage,"Painting (%.3f,%.3f,%.3f/%.3f/%d,%d,%d)", - ::paintingVoxel.x,::paintingVoxel.y,::paintingVoxel.z,::paintingVoxel.s, - (unsigned int)::paintingVoxel.red,(unsigned int)::paintingVoxel.green,(unsigned int)::paintingVoxel.blue); - drawtext(::screenWidth - 350, 50, 0.10, 0, 1.0, 0, paintMessage, 1, 1, 0); - } - - glPopMatrix(); -} - -void display(void) -{ - PerfStat("display"); - - glEnable(GL_LINE_SMOOTH); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glMatrixMode(GL_MODELVIEW); - - glPushMatrix(); { - glLoadIdentity(); - - // camera settings - if (OculusManager::isConnected()) { - myAvatar.setDisplayingHead(false); - myCamera.setUpShift (0.0f); - myCamera.setDistance (0.0f); - myCamera.setTightness (100.0f); - myCamera.setTargetPosition(myAvatar.getHeadPosition()); - myCamera.setTargetRotation(myAvatar.getBodyYaw() + myAvatar.getHeadYaw(), -myAvatar.getHeadPitch(), myAvatar.getHeadRoll()); - - } else if (myCamera.getMode() == CAMERA_MODE_MIRROR) { - myCamera.setTargetPosition(myAvatar.getSpringyHeadPosition()); - myCamera.setTargetRotation(myAvatar.getBodyYaw() - 180.0f, 0.0f, 0.0f); - - } else { - if (myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { - myCamera.setTargetPosition(myAvatar.getSpringyHeadPosition()); - myCamera.setTargetRotation(myAvatar.getAbsoluteHeadYaw()- mouseViewShiftYaw, myAvatar.getRenderPitch() + mouseViewShiftPitch, 0.0f); - } else if (myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - myCamera.setTargetPosition(myAvatar.getHeadPosition()); - myCamera.setTargetRotation(myAvatar.getBodyYaw() - mouseViewShiftYaw, mouseViewShiftPitch, 0.0f); - } - } - - // important... - myCamera.update( 1.f/FPS ); - - // Render anything (like HUD items) that we want to be in 3D but not in worldspace - /* - const float HUD_Z_OFFSET = -5.f; - glPushMatrix(); - glm::vec3 test(0.5, 0.5, 0.5); - glTranslatef(1, 1, HUD_Z_OFFSET); - drawVector(&test); - glPopMatrix(); - */ - - - // Note: whichCamera is used to pick between the normal camera myCamera for our - // main camera, vs, an alternate camera. The alternate camera we support right now - // is the viewFrustumOffsetCamera. But theoretically, we could use this same mechanism - // to add other cameras. - // - // Why have two cameras? Well, one reason is that because in the case of the renderViewFrustum() - // code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of - // myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera - Camera whichCamera = myCamera; - Camera viewFrustumOffsetCamera = myCamera; - - if (::viewFrustumFromOffset && ::frustumOn) { - - // set the camera to third-person view but offset so we can see the frustum - viewFrustumOffsetCamera.setTargetYaw(::viewFrustumOffsetYaw + myAvatar.getBodyYaw()); - viewFrustumOffsetCamera.setPitch (::viewFrustumOffsetPitch ); - viewFrustumOffsetCamera.setRoll (::viewFrustumOffsetRoll ); - viewFrustumOffsetCamera.setUpShift (::viewFrustumOffsetUp ); - viewFrustumOffsetCamera.setDistance (::viewFrustumOffsetDistance); - viewFrustumOffsetCamera.update(1.f/FPS); - whichCamera = viewFrustumOffsetCamera; - } - - // transform view according to whichCamera - // could be myCamera (if in normal mode) - // or could be viewFrustumOffsetCamera if in offset mode - // I changed the ordering here - roll is FIRST (JJV) - - glRotatef ( whichCamera.getRoll(), IDENTITY_FRONT.x, IDENTITY_FRONT.y, IDENTITY_FRONT.z); - glRotatef ( whichCamera.getPitch(), IDENTITY_RIGHT.x, IDENTITY_RIGHT.y, IDENTITY_RIGHT.z); - glRotatef (180.0 - whichCamera.getYaw(), IDENTITY_UP.x, IDENTITY_UP.y, IDENTITY_UP.z ); - - glTranslatef(-whichCamera.getPosition().x, -whichCamera.getPosition().y, -whichCamera.getPosition().z); - - // Setup 3D lights (after the camera transform, so that they are positioned in world space) - glEnable(GL_COLOR_MATERIAL); - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - - GLfloat light_position0[] = { 1.0, 1.0, 0.0, 0.0 }; - glLightfv(GL_LIGHT0, GL_POSITION, light_position0); - GLfloat ambient_color[] = { 0.7, 0.7, 0.8 }; - glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color); - GLfloat diffuse_color[] = { 0.8, 0.7, 0.7 }; - glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color); - GLfloat specular_color[] = { 1.0, 1.0, 1.0, 1.0}; - glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color); - - glMaterialfv(GL_FRONT, GL_SPECULAR, specular_color); - glMateriali(GL_FRONT, GL_SHININESS, 96); - - if (::oculusOn) { - displayOculus(whichCamera); - - } else { - displaySide(whichCamera); - glPopMatrix(); - - displayOverlay(); - } - } - - glutSwapBuffers(); - frameCount++; - - // If application has just started, report time from startup to now (first frame display) - if (justStarted) { - float startupTime = (usecTimestampNow() - usecTimestamp(&applicationStartupTime))/1000000.0; - justStarted = false; - char title[30]; - snprintf(title, 30, "Interface: %4.2f seconds", startupTime); - glutSetWindowTitle(title); - } -} - -// int version of setValue() -int setValue(int state, int *value) { - if (state == MENU_ROW_PICKED) { - *value = !(*value); - } else if (state == MENU_ROW_GET_VALUE) { - return *value; - } else { - *value = state; - } - return *value; -} - -// bool version of setValue() -int setValue(int state, bool *value) { - if (state == MENU_ROW_PICKED) { - *value = !(*value); - } else if (state == MENU_ROW_GET_VALUE) { - return *value; - } else { - *value = state; - } - return *value; -} - -int setHead(int state) { - return setValue(state, &::lookingInMirror); -} - -int setNoise(int state) { - int iRet = setValue(state, &noiseOn); - if (noiseOn) { - myAvatar.setNoise(noise); - } else { - myAvatar.setNoise(0); - } - return iRet; -} - -int setLog(int state) { - int iRet = setValue(state, &::logOn); - return iRet; -} - -int setGyroLook(int state) { - int iRet = setValue(state, &::gyroLook); - return iRet; -} - -int setFullscreen(int state) { - bool wasFullscreen = ::fullscreen; - int value = setValue(state, &::fullscreen); - if (::fullscreen != wasFullscreen) { - if (::fullscreen) { - glutFullScreen(); - - } else { - glutReshapeWindow(::screenWidth, ::screenHeight); - } - } - return value; -} - -int setVoxels(int state) { - return setValue(state, &::renderVoxels); -} - -int setStars(int state) { - return setValue(state, &::renderStarsOn); -} - -int setAtmosphere(int state) { - return setValue(state, &::renderAtmosphereOn); -} - -int setRenderAvatars(int state) { - return setValue(state, &::renderAvatarsOn); -} - -int setRenderFirstPerson(int state) { - bool value = setValue(state, &::renderFirstPersonOn); - if (state == MENU_ROW_PICKED) { - if (::renderFirstPersonOn) { - Camera::CameraFollowingAttributes a; - a.upShift = 0.0f; - a.distance = 0.0f; - a.tightness = 100.0f; - myCamera.setMode(CAMERA_MODE_FIRST_PERSON, a); - myAvatar.setDisplayingHead(false); - } else { - Camera::CameraFollowingAttributes a; - a.upShift = -0.2f; - a.distance = 1.5f; - a.tightness = 8.0f; - myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); - myAvatar.setDisplayingHead(true); - } - } - return value; -} - -int setOculus(int state) { - bool wasOn = ::oculusOn; - int value = setValue(state, &::oculusOn); - if (::oculusOn != wasOn) { - reshape(::screenWidth, ::screenHeight); - } - return value; -} - -int setStats(int state) { - return setValue(state, &::renderStatsOn); -} - -int setMenu(int state) { - return setValue(state, &::menuOn); -} - -int setRenderWarnings(int state) { - int value = setValue(state, &::renderWarningsOn); - if (state == MENU_ROW_PICKED) { - ::voxels.setRenderPipelineWarnings(::renderWarningsOn); - } - return value; -} - -int setWantResIn(int state) { - int value = setValue(state, &::wantResIn); - if (state == MENU_ROW_PICKED) { - ::myAvatar.setWantResIn(::wantResIn); - } - return value; -} - -int setWantMonochrome(int state) { - int value = setValue(state, &::wantMonochrome); - if (state == MENU_ROW_PICKED) { - ::myAvatar.setWantColor(!::wantMonochrome); - } - return value; -} - -int setWantDelta(int state) { - int value = setValue(state, &::wantDelta); - if (state == MENU_ROW_PICKED) { - ::myAvatar.setWantDelta(::wantDelta); - } - return value; -} - - -int setDisplayFrustum(int state) { - return setValue(state, &::frustumOn); -} - -int setFrustumOffset(int state) { - int value = setValue(state, &::viewFrustumFromOffset); - - // reshape so that OpenGL will get the right lens details for the camera of choice - if (state == MENU_ROW_PICKED) { - reshape(::screenWidth, ::screenHeight); - } - - return value; -} - -int setFrustumOrigin(int state) { - return setValue(state, &::cameraFrustum); -} - -int quitApp(int state) { - if (state == MENU_ROW_PICKED) { - ::terminate(); - } - return 2; // non state so menu class doesn't add "state" -} - -int setFrustumRenderMode(int state) { - if (state == MENU_ROW_PICKED) { - ::frustumDrawingMode = (::frustumDrawingMode+1)%FRUSTUM_DRAW_MODE_COUNT; - } - return ::frustumDrawingMode; -} - -int doKillLocalVoxels(int state) { - if (state == MENU_ROW_PICKED) { - ::wantToKillLocalVoxels = true; - } - return state; -} - -int doRandomizeVoxelColors(int state) { - if (state == MENU_ROW_PICKED) { - ::voxels.randomizeVoxelColors(); - } - return state; -} - -int doFalseRandomizeEveryOtherVoxelColors(int state) { - if (state == MENU_ROW_PICKED) { - ::voxels.falseColorizeRandomEveryOther(); - } - return state; -} - -int doTreeStats(int state) { - if (state == MENU_ROW_PICKED) { - ::voxels.collectStatsForTreesAndVBOs(); - } - return state; -} - -int doFalseRandomizeVoxelColors(int state) { - if (state == MENU_ROW_PICKED) { - ::voxels.falseColorizeRandom(); - } - return state; -} - -int doTrueVoxelColors(int state) { - if (state == MENU_ROW_PICKED) { - ::voxels.trueColorize(); - } - return state; -} - -int doFalseColorizeByDistance(int state) { - if (state == MENU_ROW_PICKED) { - loadViewFrustum(::viewFrustum); - voxels.falseColorizeDistanceFromView(&::viewFrustum); - } - return state; -} - -int doFalseColorizeInView(int state) { - if (state == MENU_ROW_PICKED) { - loadViewFrustum(::viewFrustum); - // we probably want to make sure the viewFrustum is initialized first - voxels.falseColorizeInView(&::viewFrustum); - } - return state; -} - - - -const char* modeAll = " - All "; -const char* modeVectors = " - Vectors "; -const char* modePlanes = " - Planes "; -const char* modeNear = " - Near "; -const char* modeFar = " - Far "; - -const char* getFrustumRenderModeName(int state) { - const char * mode; - switch (state) { - case FRUSTUM_DRAW_MODE_ALL: - mode = modeAll; - break; - case FRUSTUM_DRAW_MODE_VECTORS: - mode = modeVectors; - break; - case FRUSTUM_DRAW_MODE_PLANES: - mode = modePlanes; - break; - case FRUSTUM_DRAW_MODE_NEAR_PLANE: - mode = modeNear; - break; - case FRUSTUM_DRAW_MODE_FAR_PLANE: - mode = modeFar; - break; - } - return mode; -} - -void initMenu() { - MenuColumn *menuColumnOptions, *menuColumnRender, *menuColumnTools, *menuColumnDebug, *menuColumnFrustum; - // Options - menuColumnOptions = menu.addColumn("Options"); - menuColumnOptions->addRow("Mirror (h)", setHead); - menuColumnOptions->addRow("Noise (n)", setNoise); - menuColumnOptions->addRow("Gyro Look", setGyroLook); - menuColumnOptions->addRow("Fullscreen (f)", setFullscreen); - menuColumnOptions->addRow("Quit (q)", quitApp); - - // Render - menuColumnRender = menu.addColumn("Render"); - menuColumnRender->addRow("Voxels (V)", setVoxels); - menuColumnRender->addRow("Stars (*)", setStars); - menuColumnRender->addRow("Atmosphere (A)", setAtmosphere); - menuColumnRender->addRow("Avatars", setRenderAvatars); - menuColumnRender->addRow("First Person (p)", setRenderFirstPerson); - menuColumnRender->addRow("Oculus (o)", setOculus); - - // Tools - menuColumnTools = menu.addColumn("Tools"); - menuColumnTools->addRow("Stats (/)", setStats); - menuColumnTools->addRow("Log ", setLog); - menuColumnTools->addRow("(M)enu", setMenu); - - // Frustum Options - menuColumnFrustum = menu.addColumn("Frustum"); - menuColumnFrustum->addRow("Display (F)rustum", setDisplayFrustum); - menuColumnFrustum->addRow("Use (O)ffset Camera", setFrustumOffset); - menuColumnFrustum->addRow("Switch (C)amera", setFrustumOrigin); - menuColumnFrustum->addRow("(R)ender Mode", setFrustumRenderMode, getFrustumRenderModeName); - - // Debug - menuColumnDebug = menu.addColumn("Debug"); - menuColumnDebug->addRow("Show Render Pipeline Warnings", setRenderWarnings); - menuColumnDebug->addRow("Kill Local Voxels", doKillLocalVoxels); - menuColumnDebug->addRow("Randomize Voxel TRUE Colors", doRandomizeVoxelColors); - menuColumnDebug->addRow("FALSE Color Voxels Randomly", doFalseRandomizeVoxelColors); - menuColumnDebug->addRow("FALSE Color Voxel Every Other Randomly", doFalseRandomizeEveryOtherVoxelColors); - menuColumnDebug->addRow("FALSE Color Voxels by Distance", doFalseColorizeByDistance); - menuColumnDebug->addRow("FALSE Color Voxel Out of View", doFalseColorizeInView); - menuColumnDebug->addRow("Show TRUE Colors", doTrueVoxelColors); - menuColumnDebug->addRow("Calculate Tree Stats", doTreeStats); - menuColumnDebug->addRow("Wants Res-In", setWantResIn); - menuColumnDebug->addRow("Wants Monochrome", setWantMonochrome); - menuColumnDebug->addRow("Wants View-Delta Only", setWantDelta); -} - -void testPointToVoxel() { - float y=0; - float z=0; - float s=0.1; - for (float x=0; x<=1; x+= 0.05) { - printLog(" x=%f"); - - unsigned char red = 200; //randomColorValue(65); - unsigned char green = 200; //randomColorValue(65); - unsigned char blue = 200; //randomColorValue(65); - - unsigned char* voxelCode = pointToVoxel(x, y, z, s,red,green,blue); - printVoxelCode(voxelCode); - delete voxelCode; - printLog("\n"); - } -} - -void sendVoxelServerEraseAll() { - char message[100]; - sprintf(message,"%c%s",'Z',"erase all"); - int messageSize = strlen(message) + 1; - AgentList::getInstance()->broadcastToAgents((unsigned char*) message, messageSize, &AGENT_TYPE_VOXEL, 1); -} - -void sendVoxelServerAddScene() { - char message[100]; - sprintf(message,"%c%s",'Z',"add scene"); - int messageSize = strlen(message) + 1; - AgentList::getInstance()->broadcastToAgents((unsigned char*)message, messageSize, &AGENT_TYPE_VOXEL, 1); -} - -void shiftPaintingColor() -{ - // About the color of the paintbrush... first determine the dominant color - ::dominantColor = (::dominantColor+1)%3; // 0=red,1=green,2=blue - ::paintingVoxel.red = (::dominantColor==0)?randIntInRange(200,255):randIntInRange(40,100); - ::paintingVoxel.green = (::dominantColor==1)?randIntInRange(200,255):randIntInRange(40,100); - ::paintingVoxel.blue = (::dominantColor==2)?randIntInRange(200,255):randIntInRange(40,100); -} - -void setupPaintingVoxel() { - glm::vec3 avatarPos = myAvatar.getPosition(); - - ::paintingVoxel.x = avatarPos.z/-10.0; // voxel space x is negative z head space - ::paintingVoxel.y = avatarPos.y/-10.0; // voxel space y is negative y head space - ::paintingVoxel.z = avatarPos.x/-10.0; // voxel space z is negative x head space - ::paintingVoxel.s = 1.0/256; - - shiftPaintingColor(); -} - -void addVoxelInFrontOfAvatar() { - VoxelDetail detail; - - glm::vec3 position = (myAvatar.getPosition() + myAvatar.getCameraDirection()) * (1.0f / TREE_SCALE); - detail.s = ::mouseVoxelScale; - - detail.x = detail.s * floor(position.x / detail.s); - detail.y = detail.s * floor(position.y / detail.s); - detail.z = detail.s * floor(position.z / detail.s); - detail.red = 128; - detail.green = 128; - detail.blue = 128; - - sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, detail); - - // create the voxel locally so it appears immediately - voxels.createVoxel(detail.x, detail.y, detail.z, detail.s, detail.red, detail.green, detail.blue); -} - -void addVoxelUnderCursor() { - if (::mouseVoxel.s != 0) { - sendVoxelEditMessage(PACKET_HEADER_SET_VOXEL, ::mouseVoxel); - - // create the voxel locally so it appears immediately - voxels.createVoxel(::mouseVoxel.x, ::mouseVoxel.y, ::mouseVoxel.z, ::mouseVoxel.s, - ::mouseVoxel.red, ::mouseVoxel.green, ::mouseVoxel.blue); - } -} - -void deleteVoxelUnderCursor() { - if (::mouseVoxel.s != 0) { - sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, ::mouseVoxel); - - // delete the voxel locally so it disappears immediately - voxels.deleteVoxelAt(::mouseVoxel.x, ::mouseVoxel.y, ::mouseVoxel.z, ::mouseVoxel.s); - } -} - -const float KEYBOARD_YAW_RATE = 0.8; -const float KEYBOARD_PITCH_RATE = 0.6; -const float KEYBOARD_STRAFE_RATE = 0.03; -const float KEYBOARD_FLY_RATE = 0.08; - -void specialkeyUp(int k, int x, int y) { - if (k == GLUT_KEY_UP) { - myAvatar.setDriveKeys(FWD, 0); - myAvatar.setDriveKeys(UP, 0); - } - if (k == GLUT_KEY_DOWN) { - myAvatar.setDriveKeys(BACK, 0); - myAvatar.setDriveKeys(DOWN, 0); - } - if (k == GLUT_KEY_LEFT) { - myAvatar.setDriveKeys(LEFT, 0); - myAvatar.setDriveKeys(ROT_LEFT, 0); - } - if (k == GLUT_KEY_RIGHT) { - myAvatar.setDriveKeys(RIGHT, 0); - myAvatar.setDriveKeys(ROT_RIGHT, 0); - } -} - -void specialkey(int k, int x, int y) { - if (::chatEntryOn) { - chatEntry.specialKey(k); - return; - } - - if (k == GLUT_KEY_UP || k == GLUT_KEY_DOWN || k == GLUT_KEY_LEFT || k == GLUT_KEY_RIGHT) { - if (k == GLUT_KEY_UP) { - if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(UP, 1); - else myAvatar.setDriveKeys(FWD, 1); - } - if (k == GLUT_KEY_DOWN) { - if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(DOWN, 1); - else myAvatar.setDriveKeys(BACK, 1); - } - if (k == GLUT_KEY_LEFT) { - if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(LEFT, 1); - else myAvatar.setDriveKeys(ROT_LEFT, 1); - } - if (k == GLUT_KEY_RIGHT) { - if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) myAvatar.setDriveKeys(RIGHT, 1); - else myAvatar.setDriveKeys(ROT_RIGHT, 1); - } - } -} - -void keyUp(unsigned char k, int x, int y) { - if (::chatEntryOn) { - myAvatar.setKeyState(NO_KEY_DOWN); - return; - } - - if (k == 'e') myAvatar.setDriveKeys(UP, 0); - if (k == 'c') myAvatar.setDriveKeys(DOWN, 0); - if (k == 'w') myAvatar.setDriveKeys(FWD, 0); - if (k == 's') myAvatar.setDriveKeys(BACK, 0); - if (k == 'a') myAvatar.setDriveKeys(ROT_LEFT, 0); - if (k == 'd') myAvatar.setDriveKeys(ROT_RIGHT, 0); -} - -void toggleMouseMode(MouseMode mode) { - ::mouseMode = (::mouseMode == mode) ? NO_EDIT_MODE : mode; -} - -void key(unsigned char k, int x, int y) { - if (::chatEntryOn) { - if (chatEntry.key(k)) { - myAvatar.setKeyState(k == '\b' || k == 127 ? // backspace or delete - DELETE_KEY_DOWN : INSERT_KEY_DOWN); - myAvatar.setChatMessage(string(chatEntry.getContents().size(), SOLID_BLOCK_CHAR)); - - } else { - myAvatar.setChatMessage(chatEntry.getContents()); - chatEntry.clear(); - ::chatEntryOn = false; - } - return; - } - - // Process keypresses - - if (k == 'S') { - ::voxels.collectStatsForTreesAndVBOs(); - } - - if (k == 'q' || k == 'Q') ::terminate(); - if (k == '/') ::renderStatsOn = !::renderStatsOn; // toggle stats - if (k == '*') ::renderStarsOn = !::renderStarsOn; // toggle stars - if (k == 'V' || k == 'v') ::renderVoxels = !::renderVoxels; // toggle voxels - if (k == 'A') ::renderAtmosphereOn = !::renderAtmosphereOn; - if (k == 'F') ::frustumOn = !::frustumOn; // toggle view frustum debugging - if (k == 'C') ::cameraFrustum = !::cameraFrustum; // toggle which frustum to look at - if (k == 'O' || k == 'G') setFrustumOffset(MENU_ROW_PICKED); // toggle view frustum offset debugging - if (k == 'f') setFullscreen(!::fullscreen); - if (k == 'o') setOculus(!::oculusOn); - if (k == 'p') setRenderFirstPerson(MENU_ROW_PICKED); - if (k == '[') ::viewFrustumOffsetYaw -= 0.5; - if (k == ']') ::viewFrustumOffsetYaw += 0.5; - if (k == '{') ::viewFrustumOffsetPitch -= 0.5; - if (k == '}') ::viewFrustumOffsetPitch += 0.5; - if (k == '(') ::viewFrustumOffsetRoll -= 0.5; - if (k == ')') ::viewFrustumOffsetRoll += 0.5; - if (k == '<') ::viewFrustumOffsetDistance -= 0.5; - if (k == '>') ::viewFrustumOffsetDistance += 0.5; - if (k == ',') ::viewFrustumOffsetUp -= 0.05; - if (k == '.') ::viewFrustumOffsetUp += 0.05; - -// if (k == '|') ViewFrustum::fovAngleAdust -= 0.05; -// if (k == '\\') ViewFrustum::fovAngleAdust += 0.05; - - if (k == 'R') setFrustumRenderMode(MENU_ROW_PICKED); - - if (k == '&') { - ::paintOn = !::paintOn; // toggle paint - ::setupPaintingVoxel(); // also randomizes colors - } - if (k == '^') ::shiftPaintingColor(); // shifts randomize color between R,G,B dominant - if (k == '-') ::sendVoxelServerEraseAll(); // sends erase all command to voxel server - if (k == '%') ::sendVoxelServerAddScene(); // sends add scene command to voxel server - if (k == '1') ::mouseMode = (::mouseMode == ADD_VOXEL_MODE) ? NO_EDIT_MODE : ADD_VOXEL_MODE; - if (k == '2') ::mouseMode = (::mouseMode == DELETE_VOXEL_MODE) ? NO_EDIT_MODE : DELETE_VOXEL_MODE; - if (k == '3') ::mouseMode = (::mouseMode == COLOR_VOXEL_MODE) ? NO_EDIT_MODE : COLOR_VOXEL_MODE; - if (k == '4') addVoxelInFrontOfAvatar(); - if (k == '5') ::mouseVoxelScale /= 2; - if (k == '6') ::mouseVoxelScale *= 2; - if (k == 'n' || k == 'N') - { - noiseOn = !noiseOn; // Toggle noise - if (noiseOn) - { - myAvatar.setNoise(noise); - } - else - { - myAvatar.setNoise(0); - } - } - - if (k == 'h') { - ::lookingInMirror = !::lookingInMirror; - #ifndef _WIN32 - audio.setMixerLoopbackFlag(::lookingInMirror); - - if (::lookingInMirror) { - Camera::CameraFollowingAttributes a; - a.upShift = 0.0f; - a.distance = 0.2f; - a.tightness = 100.0f; - myCamera.setMode(CAMERA_MODE_MIRROR, a); - myAvatar.setDisplayingHead(true); - } else { - Camera::CameraFollowingAttributes a; - a.upShift = -0.2f; - a.distance = 1.5f; - a.tightness = 8.0f; - myCamera.setMode(CAMERA_MODE_THIRD_PERSON, a); - myAvatar.setDisplayingHead(true); - } - #endif - } - - if (k == 'm' || k == 'M') setMenu(MENU_ROW_PICKED); - - if (k == 'l') displayLevels = !displayLevels; - if (k == 'e') myAvatar.setDriveKeys(UP, 1); - if (k == 'c') myAvatar.setDriveKeys(DOWN, 1); - if (k == 'w') myAvatar.setDriveKeys(FWD, 1); - if (k == 's') myAvatar.setDriveKeys(BACK, 1); - if (k == ' ') reset_sensors(); - if (k == 'a') myAvatar.setDriveKeys(ROT_LEFT, 1); - if (k == 'd') myAvatar.setDriveKeys(ROT_RIGHT, 1); - - if (k == '\r') { - ::chatEntryOn = true; - myAvatar.setKeyState(NO_KEY_DOWN); - myAvatar.setChatMessage(string()); - } -} - -// Receive packets from other agents/servers and decide what to do with them! -void* networkReceive(void* args) { - sockaddr senderAddress; - ssize_t bytesReceived; - - while (!stopNetworkReceiveThread) { - // check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that - if (::wantToKillLocalVoxels) { - ::voxels.killLocalVoxels(); - ::wantToKillLocalVoxels = false; - } - - if (AgentList::getInstance()->getAgentSocket().receive(&senderAddress, incomingPacket, &bytesReceived)) { - packetCount++; - bytesCount += bytesReceived; - - switch (incomingPacket[0]) { - case PACKET_HEADER_TRANSMITTER_DATA: - // Process UDP packets that are sent to the client from local sensor devices - myAvatar.processTransmitterData(incomingPacket, bytesReceived); - break; - case PACKET_HEADER_VOXEL_DATA: - case PACKET_HEADER_VOXEL_DATA_MONOCHROME: - case PACKET_HEADER_Z_COMMAND: - case PACKET_HEADER_ERASE_VOXEL: - voxels.parseData(incomingPacket, bytesReceived); - break; - case PACKET_HEADER_ENVIRONMENT_DATA: - environment.parseData(incomingPacket, bytesReceived); - break; - case PACKET_HEADER_BULK_AVATAR_DATA: - AgentList::getInstance()->processBulkAgentData(&senderAddress, - incomingPacket, - bytesReceived); - break; - default: - AgentList::getInstance()->processAgentData(&senderAddress, incomingPacket, bytesReceived); - break; - } - } else if (!enableNetworkThread) { - break; - } - } - - if (enableNetworkThread) { - pthread_exit(0); - } - return NULL; -} - -glm::vec3 getFaceVector(BoxFace face) { - switch (face) { - case MIN_X_FACE: - return glm::vec3(-1, 0, 0); - - case MAX_X_FACE: - return glm::vec3(1, 0, 0); - - case MIN_Y_FACE: - return glm::vec3(0, -1, 0); - - case MAX_Y_FACE: - return glm::vec3(0, 1, 0); - - case MIN_Z_FACE: - return glm::vec3(0, 0, -1); - - case MAX_Z_FACE: - return glm::vec3(0, 0, 1); - } -} - -void idle(void) { - timeval check; - gettimeofday(&check, NULL); - - // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time - - if (diffclock(&lastTimeIdle, &check) > IDLE_SIMULATE_MSECS) { - - float deltaTime = 1.f/FPS; - - // update behaviors for avatar hand movement: handControl takes mouse values as input, - // and gives back 3D values modulated for smooth transitioning between interaction modes. - handControl.update(mouseX, mouseY); - myAvatar.setHandMovementValues(handControl.getValues()); - - // tell my avatar if the mouse is being pressed... - myAvatar.setMousePressed(mousePressed); - - // check what's under the mouse and update the mouse voxel - glm::vec3 mouseRayOrigin, mouseRayDirection; - viewFrustum.computePickRay(mouseX / (float)::screenWidth, mouseY / (float)::screenHeight, mouseRayOrigin, mouseRayDirection); - - // tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position - myAvatar.setMouseRay(mouseRayOrigin, mouseRayDirection); - - float distance; - BoxFace face; - ::mouseVoxel.s = 0.0f; - if (voxels.findRayIntersection(mouseRayOrigin, mouseRayDirection, ::mouseVoxel, distance, face)) { - // find the nearest voxel with the desired scale - if (::mouseVoxelScale > ::mouseVoxel.s) { - // choose the larger voxel that encompasses the one selected - ::mouseVoxel.x = ::mouseVoxelScale * floorf(::mouseVoxel.x / ::mouseVoxelScale); - ::mouseVoxel.y = ::mouseVoxelScale * floorf(::mouseVoxel.y / ::mouseVoxelScale); - ::mouseVoxel.z = ::mouseVoxelScale * floorf(::mouseVoxel.z / ::mouseVoxelScale); - ::mouseVoxel.s = ::mouseVoxelScale; - - } else if (::mouseVoxelScale < ::mouseVoxel.s) { - glm::vec3 pt = (mouseRayOrigin + mouseRayDirection * distance) / (float)TREE_SCALE - - getFaceVector(face) * (::mouseVoxelScale * 0.5f); - ::mouseVoxel.x = ::mouseVoxelScale * floorf(pt.x / ::mouseVoxelScale); - ::mouseVoxel.y = ::mouseVoxelScale * floorf(pt.y / ::mouseVoxelScale); - ::mouseVoxel.z = ::mouseVoxelScale * floorf(pt.z / ::mouseVoxelScale); - ::mouseVoxel.s = ::mouseVoxelScale; - } - - if (::mouseMode == COLOR_VOXEL_MODE) { - ::mouseVoxel.red = 0; - ::mouseVoxel.green = 255; - ::mouseVoxel.blue = 0; - - } else if (::mouseMode == DELETE_VOXEL_MODE) { - // red indicates deletion - ::mouseVoxel.red = 255; - ::mouseVoxel.green = ::mouseVoxel.blue = 0; - } - } - - // walking triggers the handControl to stop - if (myAvatar.getMode() == AVATAR_MODE_WALKING) { - handControl.stop(); - mouseViewShiftYaw *= 0.9; - mouseViewShiftPitch *= 0.9; - } - - // Read serial port interface devices - if (serialPort.active) { - serialPort.readData(); - } - - // Sample hardware, update view frustum if needed, and send avatar data to mixer/agents - updateAvatar(deltaTime); - - // read incoming packets from network - if (!enableNetworkThread) { - networkReceive(0); - } - - //loop through all the remote avatars and simulate them... - AgentList* agentList = AgentList::getInstance(); - agentList->lock(); - for(AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { - if (agent->getLinkedData() != NULL) { - Avatar *avatar = (Avatar *)agent->getLinkedData(); - avatar->simulate(deltaTime); - avatar->setMouseRay(mouseRayOrigin, mouseRayDirection); - } - } - agentList->unlock(); - - myAvatar.setGravity(getGravity(myAvatar.getPosition())); - myAvatar.simulate(deltaTime); - - // Update audio stats for procedural sounds - audio.setLastAcceleration(myAvatar.getThrust()); - audio.setLastVelocity(myAvatar.getVelocity()); - - glutPostRedisplay(); - lastTimeIdle = check; - } -} - -void reshape(int width, int height) { - ::screenWidth = width; - ::screenHeight = height; - aspectRatio = ((float)width/(float)height); // based on screen resize - - // get the lens details from the current camera - Camera& camera = ::viewFrustumFromOffset ? (::viewFrustumOffsetCamera) : (::myCamera); - float nearClip = camera.getNearClip(); - float farClip = camera.getFarClip(); - float fov; - - if (::oculusOn) { - // more magic numbers; see Oculus SDK docs, p. 32 - camera.setAspectRatio(aspectRatio *= 0.5); - camera.setFieldOfView(fov = 2 * atan((0.0468 * ::oculusDistortionScale) / 0.041) * (180 / PI)); - - // resize the render texture - if (::oculusTextureID != 0) { - glBindTexture(GL_TEXTURE_2D, ::oculusTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ::screenWidth, ::screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glBindTexture(GL_TEXTURE_2D, 0); - } - } else { - camera.setAspectRatio(aspectRatio); - camera.setFieldOfView(fov = 60); - } - - // Tell our viewFrustum about this change - ::viewFrustum.setAspectRatio(aspectRatio); - - glViewport(0, 0, width, height); // shouldn't this account for the menu??? - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - // XXXBHG - If we're in view frustum mode, then we need to do this little bit of hackery so that - // OpenGL won't clip our frustum rendering lines. This is a debug hack for sure! Basically, this makes - // the near clip a little bit closer (therefor you see more) and the far clip a little bit farther (also, - // to see more.) - if (::frustumOn) { - nearClip -= 0.01f; - farClip += 0.01f; - } - - // On window reshape, we need to tell OpenGL about our new setting - gluPerspective(fov,aspectRatio,nearClip,farClip); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); -} - -//Find and return the gravity vector at this location -glm::vec3 getGravity(glm::vec3 pos) { - // - // For now, we'll test this with a simple global lookup, but soon we will add getting this - // from the domain/voxelserver (or something similar) - // - if ((pos.x > 0.f) && - (pos.x < 10.f) && - (pos.z > 0.f) && - (pos.z < 10.f) && - (pos.y > 0.f) && - (pos.y < 3.f)) { - // If above ground plane, turn gravity on - return glm::vec3(0.f, -1.f, 0.f); - } else { - // If flying in space, turn gravity OFF - return glm::vec3(0.f, 0.f, 0.f); - } -} - -bool menuDisplayed = false; -void mouseFunc(int button, int state, int x, int y) { - bool menuFound = menu.mouseClick(x, y); - - // If we didn't previously have the menu displayed, and we did just click on the menu, then - // go into menuDisplayed mode.... - if (!::menuDisplayed && menuFound) { - ::menuDisplayed = true; - } - - // If the menu was displayed, and we're not over a menu, then leave menu mode - if (::menuDisplayed && !menuFound) { - ::menuDisplayed = false; - menu.hidePopupMenu(); - } - - // In menu displayed mode use old logic - if (::menuDisplayed) { - if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { - if (state == GLUT_DOWN && !menu.mouseClick(x, y)) { - mouseX = x; - mouseY = y; - mousePressed = 1; - } else if (state == GLUT_UP) { - mouseX = x; - mouseY = y; - mousePressed = 0; - } - } - } else { - if (button == GLUT_LEFT_BUTTON) { - mouseX = x; - mouseY = y; - - if (state == GLUT_DOWN) { - mousePressed = 1; - if (::mouseMode == ADD_VOXEL_MODE || ::mouseMode == COLOR_VOXEL_MODE) { - addVoxelUnderCursor(); - - } else if (::mouseMode == DELETE_VOXEL_MODE) { - deleteVoxelUnderCursor(); - } - } else if (state == GLUT_UP) { - mousePressed = 0; - } - } else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN && ::mouseMode != NO_EDIT_MODE) { - deleteVoxelUnderCursor(); - } - } -} - - -void motionFunc(int x, int y) { - mouseX = x; - mouseY = y; -} - -void mouseoverFunc(int x, int y){ - menu.mouseOver(x, y); - - mouseX = x; - mouseY = y; -} - -void attachNewHeadToAgent(Agent *newAgent) { - if (newAgent->getLinkedData() == NULL) { - newAgent->setLinkedData(new Avatar(false)); - } -} - -#ifndef _WIN32 -void audioMixerUpdate(in_addr_t newMixerAddress, in_port_t newMixerPort) { - audio.updateMixerParams(newMixerAddress, newMixerPort); -} -#endif +#include "Log.h" int main(int argc, const char * argv[]) { - gettimeofday(&applicationStartupTime, NULL); - printLog("Interface Startup:\n"); - - voxels.setViewFrustum(&::viewFrustum); - - shared_lib::printLog = & ::printLog; - voxels_lib::printLog = & ::printLog; - avatars_lib::printLog = & ::printLog; - - unsigned int listenPort = AGENT_SOCKET_LISTEN_PORT; - const char* portStr = getCmdOption(argc, argv, "--listenPort"); - if (portStr) { - listenPort = atoi(portStr); - } - AgentList::createInstance(AGENT_TYPE_AVATAR, listenPort); - enableNetworkThread = !cmdOptionExists(argc, argv, "--nonblocking"); - if (!enableNetworkThread) { - AgentList::getInstance()->getAgentSocket().setBlocking(false); - } - - const char* domainIP = getCmdOption(argc, argv, "--domain"); - if (domainIP) { - strcpy(DOMAIN_IP,domainIP); - } - - // Handle Local Domain testing with the --local command line - if (cmdOptionExists(argc, argv, "--local")) { - printLog("Local Domain MODE!\n"); - int ip = getLocalAddress(); - sprintf(DOMAIN_IP,"%d.%d.%d.%d", (ip & 0xFF), ((ip >> 8) & 0xFF),((ip >> 16) & 0xFF), ((ip >> 24) & 0xFF)); - } - - // the callback for our instance of AgentList is attachNewHeadToAgent - AgentList::getInstance()->linkedDataCreateCallback = &attachNewHeadToAgent; - - #ifndef _WIN32 - AgentList::getInstance()->audioMixerSocketUpdate = &audioMixerUpdate; - #endif - -#ifdef _WIN32 - WSADATA WsaData; - int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); -#endif - - // start the agentList threads - AgentList::getInstance()->startSilentAgentRemovalThread(); - AgentList::getInstance()->startDomainServerCheckInThread(); - AgentList::getInstance()->startPingUnknownAgentsThread(); - - glutInit(&argc, (char**)argv); - ::screenWidth = glutGet(GLUT_SCREEN_WIDTH); - ::screenHeight = glutGet(GLUT_SCREEN_HEIGHT); - - glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); - glutInitWindowSize(::screenWidth, ::screenHeight); - glutCreateWindow("Interface"); - printLog( "Created Display Window.\n" ); - - #ifdef _WIN32 - glewInit(); - printLog( "Glew Init complete.\n" ); - - #endif - - // we need to create a QApplication instance in order to use Qt's font rendering Application app(argc, const_cast(argv)); printLog( "Created QT Application.\n" ); - - // Before we render anything, let's set up our viewFrustumOffsetCamera with a sufficiently large - // field of view and near and far clip to make it interesting. - //viewFrustumOffsetCamera.setFieldOfView(90.0); - viewFrustumOffsetCamera.setNearClip(0.1); - viewFrustumOffsetCamera.setFarClip(500.0*TREE_SCALE); - - - initMenu(); - initDisplay(); - - glutDisplayFunc(display); - glutReshapeFunc(reshape); - glutKeyboardFunc(key); - glutKeyboardUpFunc(keyUp); - glutSpecialFunc(specialkey); - glutSpecialUpFunc(specialkeyUp); - glutMotionFunc(motionFunc); - glutPassiveMotionFunc(mouseoverFunc); - glutMouseFunc(mouseFunc); - glutIdleFunc(idle); - printLog( "Initialized Display.\n" ); - - - init(); - printLog( "Init() complete.\n" ); - - // Check to see if the user passed in a command line option for randomizing colors - if (cmdOptionExists(argc, argv, "--NoColorRandomizer")) { - wantColorRandomizer = false; - } - - // Check to see if the user passed in a command line option for loading a local - // Voxel File. If so, load it now. - const char* voxelsFilename = getCmdOption(argc, argv, "-i"); - if (voxelsFilename) { - voxels.loadVoxelsFile(voxelsFilename,wantColorRandomizer); - printLog("Local Voxel File loaded.\n"); - } - - // create thread for receipt of data via UDP - if (enableNetworkThread) { - pthread_create(&networkReceiveThread, NULL, networkReceive, NULL); - printLog("Network receive thread created.\n"); - } - - myAvatar.readAvatarDataFromFile(); - - glutTimerFunc(1000, Timer, 0); - glutMainLoop(); - + int exitCode = app.exec(); printLog("Normal exit.\n"); - ::terminate(); - return EXIT_SUCCESS; + return exitCode; } diff --git a/interface/src/ui/ChatEntry.cpp b/interface/src/ui/ChatEntry.cpp index 2b6144e76d..ad4cf70ab0 100644 --- a/interface/src/ui/ChatEntry.cpp +++ b/interface/src/ui/ChatEntry.cpp @@ -5,60 +5,71 @@ // Created by Andrzej Kapolka on 4/24/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -#include "InterfaceConfig.h" +#include #include "ChatEntry.h" +#include "InterfaceConfig.h" #include "Util.h" using namespace std; const int MAX_CONTENT_LENGTH = 140; +ChatEntry::ChatEntry() : _cursorPos(0) { +} + void ChatEntry::clear() { _contents.clear(); _cursorPos = 0; } -bool ChatEntry::key(unsigned char k) { - switch (k) { - case '\r': +bool ChatEntry::keyPressEvent(QKeyEvent* event) { + event->accept(); + switch (event->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: return false; - case '\b': + case Qt::Key_Escape: + clear(); + return false; + + case Qt::Key_Backspace: if (_cursorPos != 0) { _contents.erase(_cursorPos - 1, 1); _cursorPos--; } return true; - case 127: // delete + case Qt::Key_Delete: if (_cursorPos < _contents.size()) { _contents.erase(_cursorPos, 1); } return true; - - default: - if (_contents.size() != MAX_CONTENT_LENGTH) { - _contents.insert(_cursorPos, 1, k); - _cursorPos++; - } - return true; - } -} - -void ChatEntry::specialKey(unsigned char k) { - switch (k) { - case GLUT_KEY_LEFT: + + case Qt::Key_Left: if (_cursorPos != 0) { _cursorPos--; } - break; + return true; - case GLUT_KEY_RIGHT: + case Qt::Key_Right: if (_cursorPos != _contents.size()) { _cursorPos++; } - break; + return true; + + default: + QString text = event->text(); + if (text.isEmpty()) { + event->ignore(); + return true; + } + if (_contents.size() != MAX_CONTENT_LENGTH) { + _contents.insert(_cursorPos, 1, text.at(0).toAscii()); + _cursorPos++; + } + return true; } } @@ -74,5 +85,4 @@ void ChatEntry::render(int screenWidth, int screenHeight) { glVertex2f(20 + width, screenHeight - 165); glVertex2f(20 + width, screenHeight - 150); glEnd(); - glEnable(GL_LINE_SMOOTH); } diff --git a/interface/src/ui/ChatEntry.h b/interface/src/ui/ChatEntry.h index c2f1254c41..478641325d 100644 --- a/interface/src/ui/ChatEntry.h +++ b/interface/src/ui/ChatEntry.h @@ -11,15 +11,18 @@ #include +class QKeyEvent; + class ChatEntry { public: + ChatEntry(); + const std::string& getContents() const { return _contents; } void clear(); - bool key(unsigned char k); - void specialKey(unsigned char k); + bool keyPressEvent(QKeyEvent* event); void render(int screenWidth, int screenHeight); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0b6c3157d1..c66402e21a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -38,6 +38,7 @@ public: _headRoll(0), _headLeanSideways(0), _headLeanForward(0), + _audioLoudness(0), _handState(0), _cameraPosition(0,0,0), _cameraDirection(0,0,0),