Merge branch 'master' of https://github.com/worklist/hifi into 20736

This commit is contained in:
Thijs Wenker 2015-12-12 21:16:02 +01:00
commit cfd2c2785c
156 changed files with 3537 additions and 2435 deletions

View file

@ -36,7 +36,12 @@ if (WIN32)
if (MSVC10) if (MSVC10)
set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ") set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ")
elseif (MSVC12) elseif (MSVC12)
set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\x86 ") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(WINDOW_SDK_FOLDER "x64")
else()
set(WINDOW_SDK_FOLDER "x86")
endif()
set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}")
endif () endif ()
message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH})
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH})
@ -221,3 +226,36 @@ if (HIFI_MEMORY_DEBUGGING)
MESSAGE("-- Memory debugging is enabled") MESSAGE("-- Memory debugging is enabled")
endif (UNIX) endif (UNIX)
endif () endif ()
include_application_version()
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE)
message(STATUS "+++++ Package for deployment will be generated on this build +++++")
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/full-stack-deployment)
set(ICONPATH_INTERFACE "$INSTDIR/${PATH_INSTALL_DATA}/interface.ico")
set(ICONPATH_STACK_MANAGER "$INSTDIR/${PATH_INSTALL_DATA}/stack-manager.ico")
string(REPLACE "/" "\\\\" ICONPATH_INTERFACE ${ICONPATH_INTERFACE})
string(REPLACE "/" "\\\\" ICONPATH_STACK_MANAGER ${ICONPATH_STACK_MANAGER})
set(CPACK_PACKAGE_NAME "High Fidelity")
set(CPACK_PACKAGE_VENDOR "High Fidelity, Inc")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "High Fidelity Interface and Stack")
set(CPACK_PACKAGE_VERSION "${BUILD_SEQ}")
set(CPACK_PACKAGE_VERSION_MAJOR "${BUILD_SEQ}")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "High Fidelity-${BUILD_SEQ}")
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
set(CPACK_PACKAGE_EXECUTABLES
stack-manager "Stack Manager"
interface "Interface"
)
if (WIN32)
install(DIRECTORY ${CMAKE_BINARY_DIR}/full-stack-deployment/ DESTINATION "./")
endif (WIN32)
include(CPack)
endif ()

View file

@ -11,3 +11,4 @@ link_hifi_libraries(
include_application_version() include_application_version()
package_libraries_for_deployment() package_libraries_for_deployment()
consolidate_stack_components()

View file

@ -102,8 +102,10 @@ void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNo
packetType = message->getType(); packetType = message->getType();
} // fall through to piggyback message } // fall through to piggyback message
if (packetType == PacketType::EntityData || packetType == PacketType::EntityErase) { if (packetType == PacketType::EntityData) {
_entityViewer.processDatagram(*message, senderNode); _entityViewer.processDatagram(*message, senderNode);
} else if (packetType == PacketType::EntityErase) {
_entityViewer.processEraseMessage(*message, senderNode);
} }
} }

View file

@ -225,6 +225,8 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
newestViewFrustum.setPosition(getCameraPosition()); newestViewFrustum.setPosition(getCameraPosition());
newestViewFrustum.setOrientation(getCameraOrientation()); newestViewFrustum.setOrientation(getCameraOrientation());
newestViewFrustum.setKeyholeRadius(getKeyholeRadius());
// Also make sure it's got the correct lens details from the camera // Also make sure it's got the correct lens details from the camera
float originalFOV = getCameraFov(); float originalFOV = getCameraFov();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;

View file

@ -416,27 +416,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
return; return;
} }
/* TODO: Looking for a way to prevent locking and encoding a tree that is not
// going to result in any packets being sent...
//
// If our node is root, and the root hasn't changed, and our view hasn't changed,
// and we've already seen at least one duplicate packet, then we probably don't need
// to lock the tree and encode, because the result should be that no bytes will be
// encoded, and this will be a duplicate packet from the last one we sent...
OctreeElementPointer root = _myServer->getOctree()->getRoot();
bool skipEncode = false;
if (
(subTree == root)
&& (nodeData->getLastRootTimestamp() == root->getLastChanged())
&& !viewFrustumChanged
&& (nodeData->getDuplicatePacketCount() > 0)
) {
qDebug() << "is root, root not changed, view not changed, already seen a duplicate!"
<< "Can we skip it?";
skipEncode = true;
}
*/
float octreeSizeScale = nodeData->getOctreeSizeScale(); float octreeSizeScale = nodeData->getOctreeSizeScale();
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
@ -471,20 +450,9 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// the packet and send it // the packet and send it
completedScene = nodeData->elementBag.isEmpty(); completedScene = nodeData->elementBag.isEmpty();
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case. if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) { lastNodeDidntFit = true;
if (_packetData.hasContent() && bytesWritten == 0 &&
params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
}
} else {
// in compressed mode and we are trying to pack more... and we don't care if the _packetData has
// content or not... because in this case even if we were unable to pack any data, we want to drop
// below to our sendNow logic, but we do want to track that we attempted to pack extra
extraPackingAttempts++; extraPackingAttempts++;
if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
}
} }
nodeData->stats.encodeStopped(); nodeData->stats.encodeStopped();
@ -517,7 +485,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
} }
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
extraPackingAttempts = 0;
quint64 compressAndWriteEnd = usecTimestampNow(); quint64 compressAndWriteEnd = usecTimestampNow();
compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart); compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart);
} }
@ -526,8 +493,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// the packet doesn't have enough space to bother attempting to pack more... // the packet doesn't have enough space to bother attempting to pack more...
bool sendNow = true; bool sendNow = true;
if (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && if (!completedScene && (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) { extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS)) {
sendNow = false; // try to pack more sendNow = false; // try to pack more
} }
@ -539,6 +506,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
extraPackingAttempts = 0;
} else { } else {
// If we're in compressed mode, then we want to see if we have room for more in this wire packet. // If we're in compressed mode, then we want to see if we have room for more in this wire packet.
// but we've finalized the _packetData, so we want to start a new section, we will do that by // but we've finalized the _packetData, so we want to start a new section, we will do that by

View file

@ -15,17 +15,16 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
# 0.6 public # 0.6 public
# URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip # URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip
# URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 # URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9
# 0.7 alpha # 0.8 public
# URL https://s3.amazonaws.com/static.oculus.com/sdk-downloads/0.7.0.0/Public/Alpha/ovr_sdk_win_0.7.0.0_RC1.zip # URL http://static.oculus.com/sdk-downloads/0.8.0.0/Public/1445451746/ovr_sdk_win_0.8.0.0.zip
# URL_MD5 a562bb9d117087b2cf9d86653ea70fd8 # URL_MD5 54944b03b95149d6010f84eb701b9647
if (WIN32) if (WIN32)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip URL http://static.oculus.com/sdk-downloads/0.8.0.0/Public/1445451746/ovr_sdk_win_0.8.0.0.zip
URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 URL_MD5 54944b03b95149d6010f84eb701b9647
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""

View file

@ -7,9 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
#URL https://github.com/ValveSoftware/openvr/archive/0.9.1.zip URL https://github.com/ValveSoftware/openvr/archive/v0.9.12.zip
URL http://hifi-public.s3.amazonaws.com/dependencies/openvr-0.9.1.zip URL_MD5 c08dced68ce4e341e1467e6814ae419d
URL_MD5 f986f5a6815e9454c53c5bf58ce02fdc
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""

View file

@ -63,7 +63,11 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
if (APPLE) if (APPLE)
# NOOP
elseif (WIN32) elseif (WIN32)
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${SOURCE_DIR}/include CACHE PATH "Location of SDL2 include directory") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${SOURCE_DIR}/include CACHE PATH "Location of SDL2 include directory")
@ -75,8 +79,12 @@ elseif (WIN32)
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL") set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL")
endif() endif()
add_paths_to_fixup_libs(${${EXTERNAL_NAME_UPPER}_DLL_PATH})
else () else ()
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include/SDL2 CACHE PATH "Location of SDL2 include directory") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include/SDL2 CACHE PATH "Location of SDL2 include directory")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${INSTALL_DIR}/lib/libSDL2.so CACHE FILEPATH "Path to SDL2 library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${INSTALL_DIR}/lib/libSDL2.so CACHE FILEPATH "Path to SDL2 library")
endif () endif ()

View file

@ -8,14 +8,14 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
#set(SIXENSE_URL_MD5 "10cc8dc470d2ac1244a88cf04bc549cc") #set(SIXENSE_URL_MD5 "10cc8dc470d2ac1244a88cf04bc549cc")
#set(SIXENSE_NEW_LAYOUT 0) #set(SIXENSE_NEW_LAYOUT 0)
#set(SIXENSE_URL "http://public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip") set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip")
#set(SIXENSE_URL_MD5 "752a3901f334124e9cffc2ba4136ef7d") set(SIXENSE_URL_MD5 "752a3901f334124e9cffc2ba4136ef7d")
#set(SIXENSE_NEW_LAYOUT 1)
set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_102215.zip")
set(SIXENSE_URL_MD5 "93c3a6795cce777a0f472b09532935f1")
set(SIXENSE_NEW_LAYOUT 1) set(SIXENSE_NEW_LAYOUT 1)
#set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_102215.zip")
#set(SIXENSE_URL_MD5 "93c3a6795cce777a0f472b09532935f1")
#set(SIXENSE_NEW_LAYOUT 1)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL ${SIXENSE_URL} URL ${SIXENSE_URL}

View file

@ -0,0 +1,27 @@
macro(CONSOLIDATE_STACK_COMPONENTS)
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE AND WIN32)
# Copy all the output for this target into the common deployment location
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory $<TARGET_FILE_DIR:${TARGET_NAME}> ${CMAKE_BINARY_DIR}/full-stack-deployment
)
# Copy icon files for interface and stack manager
if (TARGET_NAME STREQUAL "interface" OR TARGET_NAME STREQUAL "stack-manager")
if (TARGET_NAME STREQUAL "interface")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/interface.ico")
set (ICON_DESTINATION_NAME "interface.ico")
elseif (TARGET_NAME STREQUAL "stack-manager")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/assets/icon.ico")
set (ICON_DESTINATION_NAME "stack-manager.ico")
endif ()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/full-stack-deployment/${ICON_DESTINATION_NAME}
)
endif ()
endif ()
endmacro()

View file

@ -10,10 +10,15 @@
# #
macro(INCLUDE_APPLICATION_VERSION) macro(INCLUDE_APPLICATION_VERSION)
#
# We are relying on Jenkins defined environment variables to determine the origin of this build
# and will only package if this is a PR or Release build
if (DEFINED ENV{JOB_ID}) if (DEFINED ENV{JOB_ID})
set (DEPLOY_PACKAGE 1)
set (BUILD_SEQ $ENV{JOB_ID}) set (BUILD_SEQ $ENV{JOB_ID})
elseif (DEFINED ENV{ghprbPullId}) elseif (DEFINED ENV{ghprbPullId})
set (BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}") set (DEPLOY_PACKAGE 1)
set (BUILD_SEQ "PR-$ENV{ghprbPullId}")
else () else ()
set(BUILD_SEQ "dev") set(BUILD_SEQ "dev")
endif () endif ()

View file

@ -1,5 +1,5 @@
# #
# CopyDllsBesideWindowsExecutable.cmake # PackageLibrariesForDeployment.cmake
# cmake/macros # cmake/macros
# #
# Copyright 2015 High Fidelity, Inc. # Copyright 2015 High Fidelity, Inc.

View file

@ -107,7 +107,7 @@ if (WIN32 AND NOT CYGWIN)
select_library_configurations(SSL_EAY) select_library_configurations(SSL_EAY)
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
elseif (MINGW) elseif (MINGW)
@ -252,6 +252,15 @@ endif ()
if (WIN32) if (WIN32)
add_paths_to_fixup_libs(${OPENSSL_DLL_PATH}) add_paths_to_fixup_libs(${OPENSSL_DLL_PATH})
#
# For some reason fixup misses the following DLL and only copies libeay32. There's gotta be a better way to handle this
# but for now resorting to the following interm solution
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE)
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy ${OPENSSL_DLL_PATH}/ssleay32.dll ${CMAKE_BINARY_DIR}/full-stack-deployment/
)
endif ()
endif () endif ()
mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES OPENSSL_SEARCH_DIRS) mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES OPENSSL_SEARCH_DIRS)

View file

@ -53,4 +53,4 @@ else()
endif() endif()
file(GLOB RUNTIME_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") file(GLOB RUNTIME_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}")
fixup_bundle("${BUNDLE_EXECUTABLE}" "${RUNTIME_PLUGINS}" "@FIXUP_LIBS@") fixup_bundle("${BUNDLE_EXECUTABLE}" "${RUNTIME_PLUGINS}" "@FIXUP_LIBS@")

View file

@ -38,3 +38,4 @@ endif (UNIX)
include_application_version() include_application_version()
package_libraries_for_deployment() package_libraries_for_deployment()
consolidate_stack_components()

View file

@ -14,7 +14,6 @@
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key. // Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
// See MAIN CONTROL, below, for what "paused" actually does. // See MAIN CONTROL, below, for what "paused" actually does.
var IK_WINDOW_AFTER_GOING_ACTIVE = 3000; // milliseconds
var OVERLAY_DATA = { var OVERLAY_DATA = {
text: "Paused:\npress any key to continue", text: "Paused:\npress any key to continue",
font: {size: 75}, font: {size: 75},
@ -31,7 +30,6 @@ function playAwayAnimation() {
return {isAway: true, isNotAway: false, isNotMoving: false, ikOverlayAlpha: 0.0}; return {isAway: true, isNotAway: false, isNotMoving: false, ikOverlayAlpha: 0.0};
} }
if (stopper) { if (stopper) {
Script.clearTimeout(stopper);
stopper = false; stopper = false;
MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); // do it now, before making new assignment MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); // do it now, before making new assignment
} }
@ -47,15 +45,14 @@ function stopAwayAnimation() {
// It cannot be as soon as we want to stop the away animation, because then things will look goofy as we come out of that animation. // It cannot be as soon as we want to stop the away animation, because then things will look goofy as we come out of that animation.
// (Imagine an away animation that sits or kneels, and then stands back up when coming out of it. If head is at the HMD, then it won't // (Imagine an away animation that sits or kneels, and then stands back up when coming out of it. If head is at the HMD, then it won't
// want to track the standing up animation.) // want to track the standing up animation.)
// Our standard anim graph flips 'awayOutroOnDone' for one frame, but it's a trigger (not an animVar) and other folks might use different graphs. // The anim graph will trigger awayOutroOnDone when awayOutro is finished.
// So... Just give us a fixed amount of time to be done with animation, before we turn ik back on.
var backToNormal = false; var backToNormal = false;
stopper = Script.setTimeout(function () { stopper = true;
backToNormal = true;
stopper = false;
}, IK_WINDOW_AFTER_GOING_ACTIVE);
function animateActive(state) { function animateActive(state) {
if (state.ikOverlayAlpha) { if (state.awayOutroOnDone) {
backToNormal = true;
stopper = false;
} else if (state.ikOverlayAlpha) {
// Once the right state gets reflected back to us, we don't need the hander any more. // Once the right state gets reflected back to us, we don't need the hander any more.
// But we are locked against handler changes during the execution of a handler, so remove asynchronously. // But we are locked against handler changes during the execution of a handler, so remove asynchronously.
Script.setTimeout(function () { MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); }, 0); Script.setTimeout(function () { MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); }, 0);
@ -63,7 +60,7 @@ function stopAwayAnimation() {
// It might be cool to "come back to life" by fading the ik overlay back in over a short time. But let's see how this goes. // It might be cool to "come back to life" by fading the ik overlay back in over a short time. But let's see how this goes.
return {isAway: false, isNotAway: true, ikOverlayAlpha: backToNormal ? 1.0 : 0.0}; // IWBNI we had a way of deleting an anim var. return {isAway: false, isNotAway: true, ikOverlayAlpha: backToNormal ? 1.0 : 0.0}; // IWBNI we had a way of deleting an anim var.
} }
activeAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateActive, ['isAway', 'isNotAway', 'isNotMoving', 'ikOverlayAlpha']); activeAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateActive, ['ikOverlayAlpha', 'awayOutroOnDone']);
} }
// OVERLAY // OVERLAY

View file

@ -202,31 +202,41 @@ function entityIsGrabbedByOther(entityID) {
} }
function getSpatialOffsetPosition(hand, spatialKey) { function getSpatialOffsetPosition(hand, spatialKey) {
var position = Vec3.ZERO;
if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) {
return spatialKey.leftRelativePosition; position = spatialKey.leftRelativePosition;
} }
if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) {
return spatialKey.rightRelativePosition; position = spatialKey.rightRelativePosition;
} }
if (spatialKey.relativePosition) { if (spatialKey.relativePosition) {
return spatialKey.relativePosition; position = spatialKey.relativePosition;
} }
return Vec3.ZERO; return position;
} }
var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y);
function getSpatialOffsetRotation(hand, spatialKey) { function getSpatialOffsetRotation(hand, spatialKey) {
var rotation = Quat.IDENTITY;
if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) {
return spatialKey.leftRelativeRotation; rotation = spatialKey.leftRelativeRotation;
} }
if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) {
return spatialKey.rightRelativeRotation; rotation = spatialKey.rightRelativeRotation;
} }
if (spatialKey.relativeRotation) { if (spatialKey.relativeRotation) {
return spatialKey.relativeRotation; rotation = spatialKey.relativeRotation;
} }
return Quat.IDENTITY; // Flip left hand
if (hand !== RIGHT_HAND) {
rotation = Quat.multiply(yFlip, rotation);
}
return rotation;
} }
function MyController(hand) { function MyController(hand) {
@ -254,6 +264,7 @@ function MyController(hand) {
this.overlayLine = null; this.overlayLine = null;
this.ignoreIK = false;
this.offsetPosition = Vec3.ZERO; this.offsetPosition = Vec3.ZERO;
this.offsetRotation = Quat.IDENTITY; this.offsetRotation = Quat.IDENTITY;
@ -853,9 +864,12 @@ function MyController(hand) {
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
// if an object is "equipped" and has a spatialKey, use it. // if an object is "equipped" and has a spatialKey, use it.
this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false;
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
} else { } else {
this.ignoreIK = false;
var objectRotation = grabbedProperties.rotation; var objectRotation = grabbedProperties.rotation;
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
@ -872,7 +886,8 @@ function MyController(hand) {
relativeRotation: this.offsetRotation, relativeRotation: this.offsetRotation,
ttl: ACTION_TTL, ttl: ACTION_TTL,
kinematic: NEAR_GRABBING_KINEMATIC, kinematic: NEAR_GRABBING_KINEMATIC,
kinematicSetVelocity: true kinematicSetVelocity: true,
ignoreIK: this.ignoreIK
}); });
if (this.actionID === NULL_ACTION_ID) { if (this.actionID === NULL_ACTION_ID) {
this.actionID = null; this.actionID = null;
@ -956,7 +971,8 @@ function MyController(hand) {
relativeRotation: this.offsetRotation, relativeRotation: this.offsetRotation,
ttl: ACTION_TTL, ttl: ACTION_TTL,
kinematic: NEAR_GRABBING_KINEMATIC, kinematic: NEAR_GRABBING_KINEMATIC,
kinematicSetVelocity: true kinematicSetVelocity: true,
ignoreIK: this.ignoreIK
}); });
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
} }
@ -981,6 +997,7 @@ function MyController(hand) {
// use a spring to pull the object to where it will be when equipped // use a spring to pull the object to where it will be when equipped
var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false;
var handRotation = this.getHandRotation(); var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition(); var handPosition = this.getHandPosition();
var targetRotation = Quat.multiply(handRotation, relativeRotation); var targetRotation = Quat.multiply(handRotation, relativeRotation);
@ -995,7 +1012,8 @@ function MyController(hand) {
linearTimeScale: EQUIP_SPRING_TIMEFRAME, linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation, targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME, angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL ttl: ACTION_TTL,
ignoreIK: ignoreIK
}); });
if (this.equipSpringID === NULL_ACTION_ID) { if (this.equipSpringID === NULL_ACTION_ID) {
this.equipSpringID = null; this.equipSpringID = null;
@ -1008,7 +1026,8 @@ function MyController(hand) {
linearTimeScale: EQUIP_SPRING_TIMEFRAME, linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation, targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME, angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL ttl: ACTION_TTL,
ignoreIK: ignoreIK
}); });
} }

View file

@ -0,0 +1,55 @@
//
// createAvatarDetector.js
//
// Created by James B. Pollack @imgntn on 12/7/2015
// Copyright 2015 High Fidelity, Inc.
//
// Run this script if you want the rats to run away from you.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var avatarDetector = null;
function createAvatarDetector() {
var detectorProperties = {
name: 'Hifi-Avatar-Detector',
type: 'Box',
position: MyAvatar.position,
dimensions: {
x: 1,
y: 2,
z: 1
},
collisionsWillMove: false,
ignoreForCollisions: true,
visible: false,
color: {
red: 255,
green: 0,
blue: 0
}
}
avatarDetector = Entities.addEntity(detectorProperties);
};
var updateAvatarDetector = function() {
// print('updating detector position' + JSON.stringify(MyAvatar.position))
Entities.editEntity(avatarDetector, {
position: MyAvatar.position
});
};
var cleanup = function() {
Script.update.disconnect(updateAvatarDetector);
Entities.deleteEntity(avatarDetector);
}
createAvatarDetector();
Script.scriptEnding.connect(cleanup);
Script.update.connect(updateAvatarDetector);

View file

@ -0,0 +1,471 @@
//
// ratCreator.js
//
// Created by James B. Pollack @imgntn on 12/7/2015
// Copyright 2015 High Fidelity, Inc.
//
// This script spawns some rats that have simple steering behaviors applied to them.
// Run it in the 'drylake' environment, or adjust all object locations to match your scene.
//
// Steering bevhaviors from ratSteer.js:
// The rats will move from a spawning point toward their nest.
// They will avoid avoider blocks moving across the alley
// They will avoid avatars running createAvatarDetector.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include('ratSteer.js');
var steer = loadSteer();
Script.include('../libraries/tween.js');
var TWEEN = loadTween();
var USE_CONSTANT_SPAWNER = true;
var RAT_SPAWNER_LOCATION = {
x: 1000.5,
y: 98,
z: 1040
};
var RAT_NEST_LOCATION = {
x: 1003.5,
y: 99,
z: 964.2
};
var RAT_DIMENSIONS = {
x: 0.22,
y: 0.32,
z: 1.14
};
var RAT_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/rat/models/rat_model.fbx';
var RAT_IDLE_ANIMATION_URL = 'http://hifi-content.s3.amazonaws.com/james/rat/animations/idle.fbx';
var RAT_WALKING_ANIMATION_URL = 'http://hifi-content.s3.amazonaws.com/james/rat/animations/walk.fbx';
var RAT_RUNNING_ANIMATION_URL = 'http://hifi-content.s3.amazonaws.com/james/rat/animations/run.fbx';
var RAT_DEATH_ANIMATION_URL = 'http://hifi-content.s3.amazonaws.com/james/rat/animations/death.fbx';
var RAT_IN_NEST_DISTANCE = 4;
//how many milliseconds between rats
var RAT_SPAWN_RATE = 2500;
var RAT_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Rats_Squeaks_Active.wav';
var ratRunningSound = SoundCache.getSound(RAT_SOUND_URL);
function playRatRunningAnimation(rat) {
var animationSettings = JSON.stringify({
running: true
});
Entities.editEntity(rat, {
animationURL: RAT_RUNNING_ANIMATION_URL,
animation: {
url: RAT_RUNNING_ANIMATION_URL,
running: true,
fps: 30
},
});
}
function playRatDeathAnimation(rat) {
var animationSettings = JSON.stringify({
running: true
});
Entities.editEntity(rat, {
animationURL: RAT_DEATH_ANIMATION_URL,
animationSettings: animationSettings
});
}
var modelRatProperties = {
name: 'rat',
type: 'Model',
modelURL: RAT_MODEL_URL,
dimensions: RAT_DIMENSIONS,
position: RAT_SPAWNER_LOCATION,
shapeType: 'Box',
damping: 0.8,
angularDamping: 0.99,
friction: 0.75,
collisionsWillMove: true,
ignoreForCollisions: false,
gravity: {
x: 0,
y: -9.8,
z: 0
},
lifetime: 30,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
};
var targetProperties = {
name: 'Hifi-Rat-Nest',
type: 'Box',
color: {
red: 0,
green: 255,
blue: 0
},
dimensions: {
x: 1,
y: 1,
z: 1
},
visible: false,
position: RAT_NEST_LOCATION
};
var target = Entities.addEntity(targetProperties);
function addRat() {
var rat = Entities.addEntity(modelRatProperties);
return rat;
}
//every sixth rat will play a sound
var RAT_SOUND_RATE = 6;
//spawn rate will be multiplied by this to clear any sounds hanging around
var RAT_SOUND_CLEAR_RATE = 3;
var rats = [];
var metaRats = [];
var ratCount = 0;
var AVOIDER_Y_HEIGHT = 99;
var FIRST_AVOIDER_START_POSITION = {
x: 1004,
y: AVOIDER_Y_HEIGHT,
z: 1019
};
var FIRST_AVOIDER_FINISH_POSITION = {
x: 997,
y: AVOIDER_Y_HEIGHT,
z: 1019
};
var SECOND_AVOIDER_START_POSITION = {
x: 998,
y: AVOIDER_Y_HEIGHT,
z: 998
};
var SECOND_AVOIDER_FINISH_POSITION = {
x: 1005,
y: AVOIDER_Y_HEIGHT,
z: 999
};
var THIRD_AVOIDER_START_POSITION = {
x: 1001.5,
y: 100,
z: 978
};
var THIRD_AVOIDER_FINISH_POSITION = {
x: 1005,
y: 100,
z: 974
};
cleanupLeftoverAvoidersBeforeStart();
var avoiders = [];
addAvoiderBlock(FIRST_AVOIDER_START_POSITION);
addAvoiderBlock(SECOND_AVOIDER_START_POSITION);
addAvoiderBlock(THIRD_AVOIDER_START_POSITION);
function addAvoiderBlock(position) {
var avoiderProperties = {
name: 'Hifi-Rat-Avoider',
type: 'Box',
color: {
red: 255,
green: 0,
blue: 255
},
dimensions: {
x: 1,
y: 1,
z: 1
},
position: position,
collisionsWillMove: false,
ignoreForCollisions: true,
visible: false
};
var avoider = Entities.addEntity(avoiderProperties);
avoiders.push(avoider);
}
tweenAvoider(avoiders[0], FIRST_AVOIDER_START_POSITION, FIRST_AVOIDER_FINISH_POSITION);
tweenAvoider(avoiders[1], SECOND_AVOIDER_START_POSITION, SECOND_AVOIDER_FINISH_POSITION);
tweenAvoider(avoiders[2], THIRD_AVOIDER_START_POSITION, THIRD_AVOIDER_FINISH_POSITION);
function tweenAvoider(entityID, startPosition, endPosition) {
var ANIMATION_DURATION = 4200;
var begin = {
x: startPosition.x,
y: startPosition.y,
z: startPosition.z
};
var end = endPosition;
var original = startPosition;
var tweenHead = new TWEEN.Tween(begin).to(end, ANIMATION_DURATION);
function updateTo() {
Entities.editEntity(entityID, {
position: {
x: begin.x,
y: begin.y,
z: begin.z
}
});
}
function updateBack() {
Entities.editEntity(entityID, {
position: {
x: begin.x,
y: begin.y,
z: begin.z
}
})
}
var tweenBack = new TWEEN.Tween(begin).to(original, ANIMATION_DURATION).onUpdate(updateBack);
tweenHead.onUpdate(function() {
updateTo()
});
tweenHead.chain(tweenBack);
tweenBack.chain(tweenHead);
tweenHead.start();
}
function updateTweens() {
TWEEN.update();
}
function createRatSoundInjector() {
var audioOptions = {
volume: 0.05,
loop: false
};
var injector = Audio.playSound(ratRunningSound, audioOptions);
return injector;
}
function moveRats() {
rats.forEach(function(rat) {
//remove the rat if its near the nest
checkDistanceFromNest(rat);
//see if there are avatars to run from
var avatarFlightVectors = steer.fleeAllAvatars(rat);
var averageAvatarFlight;
var i;
for (i = 0; i < avatarFlightVectors.length; i++) {
if (i === 0) {
averageAvatarFlight = avatarFlightVectors[0];
} else {
averageAvatarFlight = Vec3.sum(avatarFlightVectors[i - 1], avatarFlightVectors[i]);
}
}
averageAvatarFlight = Vec3.multiply(averageAvatarFlight, 1 / avatarFlightVectors.length);
//see if there are avoiders to flee
var avoidBlockVectors = steer.fleeAvoiderBlocks(rat);
var averageAvoiderFlight;
var j;
for (j = 0; j < avoidBlockVectors.length; j++) {
if (j === 0) {
averageAvoiderFlight = avoidBlockVectors[0];
} else {
averageAvoiderFlight = Vec3.sum(avoidBlockVectors[j - 1], avoidBlockVectors[j]);
}
};
averageAvoiderFlight = Vec3.multiply(averageAvoiderFlight, 1 / avoidBlockVectors.length);
//add all of the vectors and divide them by total to get average vector
//start by trying to go toward the nest
var seek = steer.arrive(rat, target);
var averageVector = seek;
var divisorCount = 1;
//if there are avatars to run away from
if (avatarFlightVectors.length > 0) {
divisorCount++;
averageVector = Vec3.sum(averageVector, averageAvatarFlight);
}
//or if there are avoider blocks to run away from
if (avoidBlockVectors.length > 0) {
divisorCount++;
averageVector = Vec3.sum(averageVector, averageAvoiderFlight);
}
averageVector = Vec3.multiply(averageVector, 1 / divisorCount);
var thisRatProps = Entities.getEntityProperties(rat, ["position", "rotation"]);
var ratPosition = thisRatProps.position;
var ratToNest = Vec3.subtract(RAT_NEST_LOCATION, ratPosition);
var ratRotation = Quat.rotationBetween(Vec3.UNIT_Z, ratToNest);
var eulerAngle = Quat.safeEulerAngles(ratRotation);
eulerAngle.x = 0;
eulerAngle.z = 0;
var constrainedRotation = Quat.fromVec3Degrees(eulerAngle);
Entities.editEntity(rat, {
velocity: averageVector,
rotation: constrainedRotation,
});
//have to make a 'meta' rat object to keep track of rats for updating sound injector locations. parenting sounds would make this easy.
var metaRat = getMetaRatByRat(rat);
if (metaRat !== undefined) {
if (metaRat.injector !== undefined) {
if (metaRat.injector.isPlaying === true) {
metaRat.injector.options = {
loop: true,
position: ratPosition
};
}
}
}
})
}
Script.update.connect(moveRats)
Script.update.connect(updateTweens);
function checkDistanceFromNest(rat) {
var ratProps = Entities.getEntityProperties(rat, "position");
var distance = Vec3.distance(ratProps.position, RAT_NEST_LOCATION);
if (distance < RAT_IN_NEST_DISTANCE) {
//at nest
removeRatFromScene(rat);
}
}
function removeRatFromScene(rat) {
var index = rats.indexOf(rat);
if (index > -1) {
rats.splice(index, 1);
Entities.deleteEntity(rat);
}
var metaRatIndex = findWithAttr(metaRats, 'rat', rat);
if (metaRatIndex > -1) {
metaRats[index].injector.stop();
metaRats.splice(index, 1);
}
}
function popRatFromStack(entityID) {
var index = rats.indexOf(entityID);
if (index > -1) {
rats.splice(index, 1);
}
var metaRatIndex = findWithAttr(metaRats, 'rat', entityID);
if (metaRatIndex > -1) {
metaRats[index].injector.stop();
metaRats.splice(index, 1);
}
}
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
}
function getMetaRatByRat(rat) {
var result = metaRats.filter(function(metaRat) {
return rat === metaRat.rat;
});
return result[0];
}
Entities.deletingEntity.connect(popRatFromStack);
function cleanupLeftoverAvoidersBeforeStart() {
//sometimes if we crash or something there could be extra avoider blocks around. clear them out.
var nearbyEntities = Entities.findEntities(RAT_SPAWNER_LOCATION, 100);
var entityIndex;
for (entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) {
var entityID = nearbyEntities[entityIndex];
var entityProps = Entities.getEntityProperties(entityID);
if (entityProps.name === 'Hifi-Rat-Avoider') {
Entities.deleteEntity(entityID);
}
}
}
function cleanup() {
while (rats.length > 0) {
Entities.deleteEntity(rats.pop());
}
while (avoiders.length > 0) {
Entities.deleteEntity(avoiders.pop());
}
Entities.deleteEntity(target);
Script.update.disconnect(moveRats);
Script.update.disconnect(updateTweens);
Entities.deletingEntity.disconnect(popRatFromStack);
Script.clearInterval(ratSpawnerInterval);
}
Script.scriptEnding.connect(cleanup);
var ratSpawnerInterval;
if (USE_CONSTANT_SPAWNER === true) {
ratSpawnerInterval = Script.setInterval(function() {
var rat = addRat();
playRatRunningAnimation(rat);
rats.push(rat);
ratCount++;
if (ratCount % RAT_SOUND_RATE === 0) {
var metaRat = {
rat: rat,
injector: createRatSoundInjector()
}
metaRats.push(metaRat);
Script.setTimeout(function() {
//if we have too many injectors hanging around there are problems
metaRat.injector.stop();
delete metaRat.injector;
}, RAT_SPAWN_RATE * RAT_SOUND_CLEAR_RATE;
}
}, RAT_SPAWN_RATE);
}

View file

@ -0,0 +1,183 @@
//
// ratSteer.js
//
// Created by James B. Pollack @imgntn on 12/7/2015
// Copyright 2015 High Fidelity, Inc.
//
// This is an example of steering behaviors that can be applied entities.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function flee(thisEntity, target) {
var targetPosition = Entities.getEntityProperties(target, "position").position;
var properties = Entities.getEntityProperties(thisEntity, ["position", "velocity"]);
var location = properties.position;
var velocity = properties.velocity;
var MAX_SPEED = 1;
var MAX_FORCE = 1;
var FLEE_RANGE = 2;
var desired = Vec3.subtract(location, targetPosition);
var d = Vec3.length(desired);
desired = Vec3.normalize(desired);
desired = Vec3.multiply(MAX_SPEED, desired);
if (d < FLEE_RANGE) {
var steer = Vec3.subtract(desired, velocity);
var steerVector = new V3(desired.x, 0, desired.z);
steer = steerVector.limit(MAX_FORCE);
return steer;
} else {
//target too far away to flee
return
}
}
function fleeAllAvatars(thisEntity) {
//print('FLEE AVATARS');
var properties = Entities.getEntityProperties(thisEntity, ["position", "velocity"]);
var location = properties.position;
var velocity = properties.velocity;
var nearbyEntities = Entities.findEntities(location, 3);
var flightVectors = [];
for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) {
var entityID = nearbyEntities[entityIndex];
var entityProps = Entities.getEntityProperties(entityID);
if (entityProps.name === 'Hifi-Avatar-Detector') {
//found an avatar to flee
var MAX_SPEED = 8;
var MAX_FORCE = 8;
var FLEE_AVATAR_RANGE = 3;
var desired = Vec3.subtract(location, entityProps.position);
var d = Vec3.length(desired);
desired = Vec3.normalize(desired);
desired = Vec3.multiply(MAX_SPEED, desired);
if (d < FLEE_AVATAR_RANGE) {
var steer = Vec3.subtract(desired, velocity);
var steerVector = new V3(desired.x, 0, desired.z);
steer = steerVector.limit(MAX_FORCE);
flightVectors.push(steer);
} else {
// target too far away from this avatar to flee
}
}
}
return flightVectors;
}
function fleeAvoiderBlocks(thisEntity) {
// print('FLEE AVOIDER BLOCKS');
var properties = Entities.getEntityProperties(thisEntity, ["position", "velocity"]);
var location = properties.position;
var velocity = properties.velocity;
var nearbyEntities = Entities.findEntities(location, 2);
var flightVectors = [];
for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) {
var entityID = nearbyEntities[entityIndex];
var entityProps = Entities.getEntityProperties(entityID);
if (entityProps.name === 'Hifi-Rat-Avoider') {
//found an avoiderblock to flee
var MAX_SPEED = 11;
var MAX_FORCE = 6;
var FLEE_AVOIDER_RANGE = 5;
var desired = Vec3.subtract(location, entityProps.position);
var d = Vec3.length(desired);
desired = Vec3.normalize(desired);
desired = Vec3.multiply(MAX_SPEED, desired);
if (d < FLEE_AVOIDER_RANGE) {
var steer = Vec3.subtract(desired, velocity);
var steerVector = new V3(desired.x, 0, desired.z);
steer = steerVector.limit(MAX_FORCE);
flightVectors.push(steer);
} else {
//target too far away from this avoider to flee
}
}
}
return flightVectors;
}
function arrive(thisEntity, target) {
var targetPosition = Entities.getEntityProperties(target, "position").position;
var properties = Entities.getEntityProperties(thisEntity, ["position", "velocity"]);
var location = properties.position;
var velocity = properties.velocity;
var MAX_SPEED = 10;
var MAX_FORCE = 6;
var ARRIVAL_DISTANCE = 2;
var desired = Vec3.subtract(targetPosition, location);
var d = Vec3.length(desired);
desired = Vec3.normalize(desired);
if (d < ARRIVAL_DISTANCE) {
var m = scale(d, 0, ARRIVAL_DISTANCE, 0, MAX_SPEED);
} else {
desired = Vec3.multiply(MAX_SPEED, desired);
}
var steer = Vec3.subtract(desired, velocity);
var steerVector = new V3(desired.x, 0, desired.z);
steer = steerVector.limit(MAX_FORCE);
return steer;
}
function V3(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return
}
V3.prototype.length = function() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
};
V3.prototype.limit = function(s) {
var len = this.length();
if (len > s && len > 0) {
this.scale(s / len);
}
return this;
};
V3.prototype.scale = function(f) {
this.x *= f;
this.y *= f;
this.z *= f;
return this;
};
var v3 = new V3();
var scale = function(value, min1, max1, min2, max2) {
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
}
loadSteer = function() {
return {
flee: flee,
fleeAllAvatars: fleeAllAvatars,
fleeAvoiderBlocks: fleeAvoiderBlocks,
arrive: arrive
};
}

View file

@ -298,7 +298,6 @@
var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame"); var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame");
var elModelAnimationLoop = document.getElementById("property-model-animation-loop"); var elModelAnimationLoop = document.getElementById("property-model-animation-loop");
var elModelAnimationHold = document.getElementById("property-model-animation-hold"); var elModelAnimationHold = document.getElementById("property-model-animation-hold");
var elModelAnimationStartAutomatically = document.getElementById("property-model-animation-start-automatically");
var elModelTextures = document.getElementById("property-model-textures"); var elModelTextures = document.getElementById("property-model-textures");
var elModelOriginalTextures = document.getElementById("property-model-original-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures");
@ -532,7 +531,6 @@
elModelAnimationLastFrame.value = properties.animation.lastFrame; elModelAnimationLastFrame.value = properties.animation.lastFrame;
elModelAnimationLoop.checked = properties.animation.loop; elModelAnimationLoop.checked = properties.animation.loop;
elModelAnimationHold.checked = properties.animation.hold; elModelAnimationHold.checked = properties.animation.hold;
elModelAnimationStartAutomatically.checked = properties.animation.startAutomatically;
elModelTextures.value = properties.textures; elModelTextures.value = properties.textures;
elModelOriginalTextures.value = properties.originalTextures; elModelOriginalTextures.value = properties.originalTextures;
} else if (properties.type == "Web") { } else if (properties.type == "Web") {
@ -785,7 +783,6 @@
elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame'));
elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop'));
elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold'));
elModelAnimationStartAutomatically.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'startAutomatically'));
elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures'));
@ -1348,12 +1345,6 @@
<input type='checkbox' id="property-model-animation-hold"> <input type='checkbox' id="property-model-animation-hold">
</span> </span>
</div> </div>
<div class="model-section property">
<span class="label">Animation Start Automatically</span>
<span class="value">
<input type='checkbox' id="property-model-animation-start-automatically">
</span>
</div>
<div class="model-section property"> <div class="model-section property">
<div class="label">Textures</div> <div class="label">Textures</div>
<div class="value"> <div class="value">

View file

@ -74,7 +74,9 @@ var keysToIgnore = [
'shapeType', 'shapeType',
'isEmitting', 'isEmitting',
'sittingPoints', 'sittingPoints',
'originalTextures' 'originalTextures',
'parentJointIndex',
'parentID'
]; ];
var individualKeys = []; var individualKeys = [];

View file

@ -128,7 +128,6 @@ function setUp() {
blue: 255 blue: 255
}, },
lifespan: 5.0, lifespan: 5.0,
visible: false,
locked: false, locked: false,
isEmitting: true, isEmitting: true,
lifetime: 3600 // 1 hour; just in case lifetime: 3600 // 1 hour; just in case

View file

@ -66,21 +66,6 @@
max2: 15 max2: 15
} }
var BOW_SPATIAL_KEY = {
leftRelativePosition: {
x: 0.05,
y: 0.06,
z: -0.05
},
rightRelativePosition: {
x: -0.05,
y: 0.06,
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
}
var USE_DEBOUNCE = false; var USE_DEBOUNCE = false;
var TRIGGER_CONTROLS = [ var TRIGGER_CONTROLS = [
@ -168,11 +153,9 @@
var handToDisable = this.initialHand === 'right' ? 'left' : 'right'; var handToDisable = this.initialHand === 'right' ? 'left' : 'right';
Messages.sendMessage('Hifi-Hand-Disabler', handToDisable); Messages.sendMessage('Hifi-Hand-Disabler', handToDisable);
setEntityCustomData('grabbableKey', this.entityID, { var data = getEntityCustomData('grabbableKey', this.entityID, {});
grabbable: false, data.grabbable = false;
invertSolidWhileHeld: true, setEntityCustomData('grabbableKey', this.entityID, data);
spatialKey: BOW_SPATIAL_KEY
});
}, },
continueNearGrab: function() { continueNearGrab: function() {
@ -226,11 +209,10 @@
this.isGrabbed = false; this.isGrabbed = false;
this.stringDrawn = false; this.stringDrawn = false;
this.deleteStrings(); this.deleteStrings();
setEntityCustomData('grabbableKey', this.entityID, {
grabbable: true, var data = getEntityCustomData('grabbableKey', this.entityID, {});
invertSolidWhileHeld: true, data.grabbable = true;
spatialKey: BOW_SPATIAL_KEY setEntityCustomData('grabbableKey', this.entityID, data);
});
Entities.deleteEntity(this.preNotchString); Entities.deleteEntity(this.preNotchString);
Entities.deleteEntity(this.arrow); Entities.deleteEntity(this.arrow);
this.aiming = false; this.aiming = false;

View file

@ -49,14 +49,14 @@ var bow = Entities.addEntity({
invertSolidWhileHeld: true, invertSolidWhileHeld: true,
spatialKey: { spatialKey: {
leftRelativePosition: { leftRelativePosition: {
x: 0.05, x: -0.02,
y: 0.06, y: 0.08,
z: -0.05 z: 0.09
}, },
rightRelativePosition: { relativePosition: {
x: -0.05, x: 0.02,
y: 0.06, y: 0.08,
z: -0.05 z: 0.09
}, },
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90) relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
} }

View file

@ -53,8 +53,13 @@ var wand = Entities.addEntity({
y: 0.1, y: 0.1,
z: 0 z: 0
}, },
relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 90) relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, -90)
} }
} }
}) })
}); });
function scriptEnding() {
Entities.deleteEntity(wand);
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -25,7 +25,7 @@
//we are creating lights that we don't want to get stranded so lets make sure that we can get rid of them //we are creating lights that we don't want to get stranded so lets make sure that we can get rid of them
var startTime = Date.now(); var startTime = Date.now();
//if you're going to be using this in a dungeon or something and holding it for a long time, increase this lifetime value. //if you're going to be using this in a dungeon or something and holding it for a long time, increase this lifetime value.
var LIFETIME = 25; var LIFETIME = 100;
var MSEC_PER_SEC = 1000.0; var MSEC_PER_SEC = 1000.0;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
@ -85,9 +85,14 @@
this.hand = 'LEFT'; this.hand = 'LEFT';
}, },
startNearGrab: function() { startNearGrab: function(entityID) {
if (!this.hasSpotlight) { if (!this.hasSpotlight) {
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
//this light casts the beam //this light casts the beam
this.spotlight = Entities.addEntity({ this.spotlight = Entities.addEntity({
type: "Light", type: "Light",
@ -97,6 +102,7 @@
y: 2, y: 2,
z: 20 z: 20
}, },
parentID: this.entityID,
color: { color: {
red: 255, red: 255,
green: 255, green: 255,
@ -105,7 +111,9 @@
intensity: 2, intensity: 2,
exponent: 0.3, exponent: 0.3,
cutoff: 20, cutoff: 20,
lifetime: LIFETIME lifetime: LIFETIME,
position: lightTransform.p,
rotation: lightTransform.q,
}); });
//this light creates the effect of a bulb at the end of the flashlight //this light creates the effect of a bulb at the end of the flashlight
@ -116,6 +124,7 @@
y: 0.25, y: 0.25,
z: 0.25 z: 0.25
}, },
parentID: this.entityID,
isSpotlight: false, isSpotlight: false,
color: { color: {
red: 255, red: 255,
@ -123,8 +132,11 @@
blue: 255 blue: 255
}, },
exponent: 0, exponent: 0,
lifetime: LIFETIME,
cutoff: 90, // in degrees cutoff: 90, // in degrees
lifetime: LIFETIME position: glowLightTransform.p,
rotation: glowLightTransform.q,
}); });
this.hasSpotlight = true; this.hasSpotlight = true;
@ -142,7 +154,6 @@
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten //only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
this.setWhichHand(); this.setWhichHand();
} else { } else {
this.updateLightPositions();
this.changeLightWithTriggerPressure(this.whichHand); this.changeLightWithTriggerPressure(this.whichHand);
} }
}, },
@ -159,29 +170,7 @@
this.lightOn = false; this.lightOn = false;
} }
}, },
updateLightPositions: function() {
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
//move the two lights along the vectors we set above
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
//move them with the entity model
Entities.editEntity(this.spotlight, {
position: lightTransform.p,
rotation: lightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
Entities.editEntity(this.glowLight, {
position: glowLightTransform.p,
rotation: glowLightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
},
changeLightWithTriggerPressure: function(flashLightHand) { changeLightWithTriggerPressure: function(flashLightHand) {
if (flashLightHand === 'LEFT') { if (flashLightHand === 'LEFT') {

View file

@ -28,10 +28,10 @@ var pistol = Entities.addEntity({
spatialKey: { spatialKey: {
relativePosition: { relativePosition: {
x: 0, x: 0,
y: 0, y: 0.05,
z: 0 z: -0.08
}, },
relativeRotation: Quat.fromPitchYawRollDegrees(45, 90, 0) relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0)
}, },
invertSolidWhileHeld: true invertSolidWhileHeld: true
} }

View file

@ -12,31 +12,57 @@
Script.include("cookies.js"); Script.include("cookies.js");
var audioOptions = new AudioEffectOptions({ var audioOptions = new AudioEffectOptions({
maxRoomSize: 50, bandwidth: 10000,
preDelay: 20,
lateDelay: 0,
reverbTime: 2,
earlyDiffusion: 100,
lateDiffusion: 100,
roomSize: 50, roomSize: 50,
reverbTime: 4, density: 100,
damping: 0.50, bassMult: 1.5,
inputBandwidth: 0.8, bassFreq: 250,
earlyLevel: 0, highGain: -6,
tailLevel: 0, highFreq: 3000,
dryLevel: -6, modRate: 2.3,
wetLevel: -6 modDepth: 50,
earlyGain: 0,
lateGain: 0,
earlyMixLeft: 20,
earlyMixRight: 20,
lateMixLeft: 90,
lateMixRight: 90,
wetDryMix: 50,
}); });
AudioDevice.setReverbOptions(audioOptions); AudioDevice.setReverbOptions(audioOptions);
AudioDevice.setReverb(true); AudioDevice.setReverb(true);
print("Reverb is ON."); print("Reverb is ON.");
var panel = new Panel(10, 200); var panel = new Panel(10, 160);
var parameters = [ var parameters = [
{ name: "roomSize", min: 0, max: 100, units: " feet" }, { name: "bandwidth", min: 1000, max: 12000, units: " Hz" },
{ name: "reverbTime", min: 0, max: 10, units: " sec" }, { name: "preDelay", min: 0, max: 333, units: " ms" },
{ name: "damping", min: 0, max: 1, units: " " }, { name: "lateDelay", min: 0, max: 166, units: " ms" },
{ name: "inputBandwidth", min: 0, max: 1, units: " " }, { name: "reverbTime", min: 0.1, max: 10, units: " seconds" },
{ name: "earlyLevel", min: -48, max: 0, units: " dB" }, { name: "earlyDiffusion", min: 0, max: 100, units: " percent" },
{ name: "tailLevel", min: -48, max: 0, units: " dB" }, { name: "lateDiffusion", min: 0, max: 100, units: " percent" },
{ name: "wetLevel", min: -48, max: 0, units: " dB" }, { name: "roomSize", min: 0, max: 100, units: " percent" },
{ name: "density", min: 0, max: 100, units: " percent" },
{ name: "bassMult", min: 0.1, max: 4, units: " ratio" },
{ name: "bassFreq", min: 10, max: 500, units: " Hz" },
{ name: "highGain", min: -24, max: 0, units: " dB" },
{ name: "highFreq", min: 1000, max: 12000, units: " Hz" },
{ name: "modRate", min: 0.1, max: 10, units: " Hz" },
{ name: "modDepth", min: 0, max: 100, units: " percent" },
{ name: "earlyGain", min: -96, max: 24, units: " dB" },
{ name: "lateGain", min: -96, max: 24, units: " dB" },
{ name: "earlyMixLeft", min: 0, max: 100, units: " percent" },
{ name: "earlyMixRight", min: 0, max: 100, units: " percent" },
{ name: "lateMixLeft", min: 0, max: 100, units: " percent" },
{ name: "lateMixRight", min: 0, max: 100, units: " percent" },
{ name: "wetDryMix", min: 0, max: 100, units: " percent" },
] ]
function setter(name) { function setter(name) {
@ -48,7 +74,7 @@ function getter(name) {
} }
function displayer(units) { function displayer(units) {
return function(value) { return (value).toFixed(1) + units; }; return function(value) { return (value).toFixed(1) + units; }
} }
// create a slider for each parameter // create a slider for each parameter

View file

@ -100,6 +100,15 @@ else()
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
endif() endif()
# These are external plugins, but we need to do the 'add dependency' here so that their
# binary directories get added to the fixup path
add_dependency_external_projects(sixense)
add_dependency_external_projects(sdl2)
if (WIN32)
add_dependency_external_projects(OpenVR)
endif()
# disable /OPT:REF and /OPT:ICF for the Debug builds # disable /OPT:REF and /OPT:ICF for the Debug builds
# This will prevent the following linker warnings # This will prevent the following linker warnings
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
@ -194,7 +203,6 @@ else (APPLE)
# link target to external libraries # link target to external libraries
if (WIN32) if (WIN32)
# target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib)
target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib) target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib)
else (WIN32) else (WIN32)
# Nothing else required on linux apparently # Nothing else required on linux apparently
@ -202,3 +210,4 @@ else (APPLE)
endif (APPLE) endif (APPLE)
package_libraries_for_deployment() package_libraries_for_deployment()
consolidate_stack_components()

View file

@ -68,7 +68,6 @@
#include <HFBackEvent.h> #include <HFBackEvent.h>
#include <InfoView.h> #include <InfoView.h>
#include <input-plugins/InputPlugin.h> #include <input-plugins/InputPlugin.h>
#include <input-plugins/Joystick.h> // this should probably be removed
#include <controllers/UserInputMapper.h> #include <controllers/UserInputMapper.h>
#include <controllers/StateController.h> #include <controllers/StateController.h>
#include <LogHandler.h> #include <LogHandler.h>
@ -195,6 +194,8 @@ static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
static const unsigned int THROTTLED_SIM_FRAMERATE = 15; static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check for entities that aren't ready for simulation
#ifndef __APPLE__ #ifndef __APPLE__
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
#else #else
@ -350,6 +351,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<UserInputMapper>(); DependencyManager::set<UserInputMapper>();
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>(); DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
DependencyManager::set<InterfaceParentFinder>(); DependencyManager::set<InterfaceParentFinder>();
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
return true; return true;
} }
@ -369,7 +371,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_frameCount(0), _frameCount(0),
_fps(60.0f), _fps(60.0f),
_physicsEngine(new PhysicsEngine(Vectors::ZERO)), _physicsEngine(new PhysicsEngine(Vectors::ZERO)),
_entities(true, this, this),
_entityClipboardRenderer(false, this, this), _entityClipboardRenderer(false, this, this),
_entityClipboard(new EntityTree()), _entityClipboard(new EntityTree()),
_lastQueriedTime(usecTimestampNow()), _lastQueriedTime(usecTimestampNow()),
@ -862,7 +863,7 @@ void Application::cleanupBeforeQuit() {
} }
_keyboardFocusHighlight = nullptr; _keyboardFocusHighlight = nullptr;
_entities.clear(); // this will allow entity scripts to properly shutdown getEntities()->clear(); // this will allow entity scripts to properly shutdown
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -873,7 +874,7 @@ void Application::cleanupBeforeQuit() {
// tell the packet receiver we're shutting down, so it can drop packets // tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true); nodeList->getPacketReceiver().setShouldDropPackets(true);
_entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
ScriptEngine::stopAllScripts(this); // stop all currently running global scripts ScriptEngine::stopAllScripts(this); // stop all currently running global scripts
// first stop all timers directly or by invokeMethod // first stop all timers directly or by invokeMethod
@ -919,7 +920,7 @@ void Application::emptyLocalCache() {
} }
Application::~Application() { Application::~Application() {
EntityTreePointer tree = _entities.getTree(); EntityTreePointer tree = getEntities()->getTree();
tree->setSimulation(NULL); tree->setSimulation(NULL);
_octreeProcessor.terminate(); _octreeProcessor.terminate();
@ -1122,29 +1123,6 @@ void Application::paintGL() {
_inPaint = true; _inPaint = true;
Finally clearFlagLambda([this] { _inPaint = false; }); Finally clearFlagLambda([this] { _inPaint = false; });
// Some LOD-like controls need to know a smoothly varying "potential" frame rate that doesn't
// include time waiting for vsync, and which can report a number above target if we've got the headroom.
// For example, if we're shooting for 75fps and paintWait is 3.3333ms (= 75% * 13.33ms), our deducedNonVSyncFps
// would be 100fps. In principle, a paintWait of zero would have deducedNonVSyncFps=75.
// Here we make a guess for deducedNonVSyncFps = 1 / deducedNonVSyncPeriod.
//
// Time between previous paintGL call and this one, which can vary not only with vSync misses, but also with QT timing.
// We're using this as a proxy for the time between vsync and displayEnd, below. (Not exact, but tends to be the same over time.)
// This is not the same as update(deltaTime), because the latter attempts to throttle to 60hz and also clamps to 1/4 second.
const float actualPeriod = diff / (float)USECS_PER_SECOND; // same as 1/instantaneousFps but easier for compiler to optimize
// Note that _lastPaintWait (stored at end of last call) is for the same paint cycle.
float deducedNonVSyncPeriod = actualPeriod - _lastPaintWait + _marginForDeducedFramePeriod; // plus a some non-zero time for machinery we can't measure
// We don't know how much time to allow for that, but if we went over the target period, we know it's at least the portion
// of paintWait up to the next vSync. This gives us enough of a penalty so that when actualPeriod crosses two cycles,
// the key part (and not an exagerated part) of _lastPaintWait is accounted for.
const float targetPeriod = getTargetFramePeriod();
if (_lastPaintWait > EPSILON && actualPeriod > targetPeriod) {
// Don't use C++ remainder(). It's authors are mathematically insane.
deducedNonVSyncPeriod += fmod(actualPeriod, _lastPaintWait);
}
_lastDeducedNonVSyncFps = 1.0f / deducedNonVSyncPeriod;
_lastInstantaneousFps = instantaneousFps;
auto displayPlugin = getActiveDisplayPlugin(); auto displayPlugin = getActiveDisplayPlugin();
// FIXME not needed anymore? // FIXME not needed anymore?
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();
@ -1401,7 +1379,6 @@ void Application::paintGL() {
Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture)); Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture));
_lockedFramebufferMap[finalTexture] = scratchFramebuffer; _lockedFramebufferMap[finalTexture] = scratchFramebuffer;
uint64_t displayStart = usecTimestampNow();
Q_ASSERT(isCurrentContext(_offscreenContext->getContext())); Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
{ {
PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene"); PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene");
@ -1410,9 +1387,6 @@ void Application::paintGL() {
} }
Q_ASSERT(isCurrentContext(_offscreenContext->getContext())); Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
uint64_t displayEnd = usecTimestampNow();
const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs
_lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND;
} }
{ {
@ -1423,6 +1397,14 @@ void Application::paintGL() {
batch.resetStages(); batch.resetStages();
}); });
} }
// Some LOD-like controls need to know a smoothly varying "potential" frame rate that doesn't
// include time waiting for sync, and which can report a number above target if we've got the headroom.
// In my tests, the following is mostly less than 0.5ms, and never more than 3ms. I don't think its worth measuring during runtime.
const float paintWaitAndQTTimerAllowance = 0.001f; // seconds
// Store both values now for use by next cycle.
_lastInstantaneousFps = instantaneousFps;
_lastUnsynchronizedFps = 1.0f / (((usecTimestampNow() - now) / (float)USECS_PER_SECOND) + paintWaitAndQTTimerAllowance);
} }
void Application::runTests() { void Application::runTests() {
@ -1993,7 +1975,7 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
event->buttons(), event->modifiers()); event->buttons(), event->modifiers());
_entities.mouseMoveEvent(&mappedEvent, deviceID); getEntities()->mouseMoveEvent(&mappedEvent, deviceID);
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent, deviceID); // send events to any registered scripts _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent, deviceID); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it // if one of our scripts have asked to capture this event, then stop processing it
@ -2019,7 +2001,7 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
event->buttons(), event->modifiers()); event->buttons(), event->modifiers());
if (!_aboutToQuit) { if (!_aboutToQuit) {
_entities.mousePressEvent(&mappedEvent, deviceID); getEntities()->mousePressEvent(&mappedEvent, deviceID);
} }
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
@ -2064,7 +2046,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
event->buttons(), event->modifiers()); event->buttons(), event->modifiers());
if (!_aboutToQuit) { if (!_aboutToQuit) {
_entities.mouseReleaseEvent(&mappedEvent, deviceID); getEntities()->mouseReleaseEvent(&mappedEvent, deviceID);
} }
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
@ -2387,7 +2369,7 @@ void Application::calibrateEyeTracker5Points() {
bool Application::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs) { bool Application::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs) {
QVector<EntityItemPointer> entities; QVector<EntityItemPointer> entities;
auto entityTree = _entities.getTree(); auto entityTree = getEntities()->getTree();
auto exportTree = std::make_shared<EntityTree>(); auto exportTree = std::make_shared<EntityTree>();
exportTree->createRootElement(); exportTree->createRootElement();
@ -2431,7 +2413,7 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) { bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
QVector<EntityItemPointer> entities; QVector<EntityItemPointer> entities;
_entities.getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities); getEntities()->getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities);
if (entities.size() > 0) { if (entities.size() > 0) {
glm::vec3 root(x, y, z); glm::vec3 root(x, y, z);
@ -2499,7 +2481,7 @@ bool Application::importEntities(const QString& urlOrFilename) {
} }
QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) { QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) {
return _entityClipboard->sendEntities(&_entityEditSender, _entities.getTree(), x, y, z); return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z);
} }
void Application::initDisplay() { void Application::initDisplay() {
@ -2538,13 +2520,13 @@ void Application::init() {
// fire off an immediate domain-server check in now that settings are loaded // fire off an immediate domain-server check in now that settings are loaded
DependencyManager::get<NodeList>()->sendDomainServerCheckIn(); DependencyManager::get<NodeList>()->sendDomainServerCheckIn();
_entities.init(); getEntities()->init();
_entities.setViewFrustum(getViewFrustum()); getEntities()->setViewFrustum(getViewFrustum());
ObjectMotionState::setShapeManager(&_shapeManager); ObjectMotionState::setShapeManager(&_shapeManager);
_physicsEngine->init(); _physicsEngine->init();
EntityTreePointer tree = _entities.getTree(); EntityTreePointer tree = getEntities()->getTree();
_entitySimulation.init(tree, _physicsEngine, &_entityEditSender); _entitySimulation.init(tree, _physicsEngine, &_entityEditSender);
tree->setSimulation(&_entitySimulation); tree->setSimulation(&_entitySimulation);
@ -2552,11 +2534,11 @@ void Application::init() {
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity,
&_entities, &EntityTreeRenderer::entityCollisionWithEntity); getEntities(), &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
// of events related clicking, hovering over, and entering entities // of events related clicking, hovering over, and entering entities
_entities.connectSignalsToSlots(entityScriptingInterface.data()); getEntities()->connectSignalsToSlots(entityScriptingInterface.data());
_entityClipboardRenderer.init(); _entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum()); _entityClipboardRenderer.setViewFrustum(getViewFrustum());
@ -2781,6 +2763,8 @@ void Application::reloadResourceCaches() {
DependencyManager::get<TextureCache>()->refreshAll(); DependencyManager::get<TextureCache>()->refreshAll();
DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models
getMyAvatar()->resetFullAvatarURL();
} }
void Application::rotationModeChanged() { void Application::rotationModeChanged() {
@ -2905,19 +2889,19 @@ void Application::update(float deltaTime) {
_avatarUpdate->synchronousProcess(); _avatarUpdate->synchronousProcess();
{ if (_physicsEnabled) {
PerformanceTimer perfTimer("physics"); PerformanceTimer perfTimer("physics");
static VectorOfMotionStates motionStates; static VectorOfMotionStates motionStates;
_entitySimulation.getObjectsToDelete(motionStates); _entitySimulation.getObjectsToDelete(motionStates);
_physicsEngine->deleteObjects(motionStates); _physicsEngine->deleteObjects(motionStates);
_entities.getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_entitySimulation.getObjectsToAdd(motionStates); _entitySimulation.getObjectsToAdd(motionStates);
_physicsEngine->addObjects(motionStates); _physicsEngine->addObjects(motionStates);
}); });
_entities.getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_entitySimulation.getObjectsToChange(motionStates); _entitySimulation.getObjectsToChange(motionStates);
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
_entitySimulation.setObjectsToChange(stillNeedChange); _entitySimulation.setObjectsToChange(stillNeedChange);
@ -2935,12 +2919,12 @@ void Application::update(float deltaTime) {
myAvatar->prepareForPhysicsSimulation(); myAvatar->prepareForPhysicsSimulation();
_entities.getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation(); _physicsEngine->stepSimulation();
}); });
if (_physicsEngine->hasOutgoingChanges()) { if (_physicsEngine->hasOutgoingChanges()) {
_entities.getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID()); _entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID());
avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges()); avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges());
}); });
@ -2955,12 +2939,13 @@ void Application::update(float deltaTime) {
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.) // deadlock.)
_entitySimulation.handleCollisionEvents(collisionEvents); _entitySimulation.handleCollisionEvents(collisionEvents);
// NOTE: the _entities.update() call below will wait for lock // NOTE: the getEntities()->update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation). // and will simulate entity motion (the EntityTree has been given an EntitySimulation).
_entities.update(); // update the models... getEntities()->update(); // update the models...
} }
myAvatar->harvestResultsFromPhysicsSimulation(); myAvatar->harvestResultsFromPhysicsSimulation();
myAvatar->simulateAttachments(deltaTime);
} }
} }
@ -3093,6 +3078,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); _octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@ -3253,6 +3239,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
bool Application::isHMDMode() const { bool Application::isHMDMode() const {
return getActiveDisplayPlugin()->isHmd(); return getActiveDisplayPlugin()->isHmd();
} }
float Application::getTargetFrameRate() { return getActiveDisplayPlugin()->getTargetFrameRate(); }
QRect Application::getDesirableApplicationGeometry() { QRect Application::getDesirableApplicationGeometry() {
QRect applicationGeometry = getWindow()->geometry(); QRect applicationGeometry = getWindow()->geometry();
@ -3444,10 +3431,10 @@ namespace render {
// Background rendering decision // Background rendering decision
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
auto skybox = model::SkyboxPointer();
if (skyStage->getBackgroundMode() == model::SunSkyStage::NO_BACKGROUND) { if (skyStage->getBackgroundMode() == model::SunSkyStage::NO_BACKGROUND) {
// this line intentionally left blank
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_DOME) { } else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_DOME) {
if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
PerformanceTimer perfTimer("stars"); PerformanceTimer perfTimer("stars");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::payloadRender<BackgroundRenderData>() ... stars..."); "Application::payloadRender<BackgroundRenderData>() ... stars...");
@ -3513,10 +3500,9 @@ namespace render {
} }
} else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) { } else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) {
PerformanceTimer perfTimer("skybox"); PerformanceTimer perfTimer("skybox");
auto skybox = skyStage->getSkybox();
skybox = skyStage->getSkybox();
if (skybox) { if (skybox) {
skybox->render(batch, *(qApp->getDisplayViewFrustum())); skybox->render(batch, *(args->_viewFrustum));
} }
} }
} }
@ -3779,13 +3765,19 @@ void Application::clearDomainOctreeDetails() {
}); });
// reset the model renderer // reset the model renderer
_entities.clear(); getEntities()->clear();
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME);
} }
void Application::domainChanged(const QString& domainHostname) { void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle(); updateWindowTitle();
clearDomainOctreeDetails(); clearDomainOctreeDetails();
_domainConnectionRefusals.clear(); _domainConnectionRefusals.clear();
// disable physics until we have enough information about our new location to not cause craziness.
_physicsEnabled = false;
} }
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) { void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
@ -3893,6 +3885,34 @@ void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNode
} }
} }
bool Application::nearbyEntitiesAreReadyForPhysics() {
// this is used to avoid the following scenario:
// A table has some items sitting on top of it. The items are at rest, meaning they aren't active in bullet.
// Someone logs in close to the table. They receive information about the items on the table before they
// receive information about the table. The items are very close to the avatar's capsule, so they become
// activated in bullet. This causes them to fall to the floor, because the table's shape isn't yet in bullet.
EntityTreePointer entityTree = getEntities()->getTree();
if (!entityTree) {
return false;
}
QVector<EntityItemPointer> entities;
entityTree->withReadLock([&] {
AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE));
entityTree->findEntities(box, entities);
});
foreach (EntityItemPointer entity, entities) {
if (!entity->isReadyToComputeShape()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
return false;
}
}
return true;
}
int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) { int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server // But, also identify the sender, and keep track of the contained jurisdiction root for this server
@ -3938,7 +3958,22 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
}); });
}); });
if (!_physicsEnabled) {
if (nearbyEntitiesAreReadyForPhysics()) {
// These stats packets are sent in between full sends of a scene.
// We keep physics disabled until we've recieved a full scene and everything near the avatar in that
// scene is ready to compute its collision shape.
_physicsEnabled = true;
getMyAvatar()->updateMotionBehaviorFromMenu();
} else {
auto characterController = getMyAvatar()->getCharacterController();
if (characterController) {
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
// a non-loading collision hull.
characterController->setEnabled(false);
}
}
}
return statsMessageLength; return statsMessageLength;
} }
@ -3987,20 +4022,13 @@ void Application::saveScripts() {
settings.endArray(); settings.endArray();
} }
QScriptValue joystickToScriptValue(QScriptEngine *engine, Joystick* const &in) {
return engine->newQObject(in);
}
void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) {
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application. // we can use the same ones from the application.
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setPacketSender(&_entityEditSender);
entityScriptingInterface->setEntityTree(_entities.getTree()); entityScriptingInterface->setEntityTree(getEntities()->getTree());
// AvatarManager has some custom types // AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine); AvatarManager::registerMetaTypes(scriptEngine);
@ -4021,7 +4049,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&))); connect(scriptEngine, &ScriptEngine::finished, this, &Application::scriptFinished, Qt::DirectConnection);
connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool))); connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool)));
connect(scriptEngine, SIGNAL(reloadScript(const QString&, bool)), this, SLOT(reloadScript(const QString&, bool))); connect(scriptEngine, SIGNAL(reloadScript(const QString&, bool)), this, SLOT(reloadScript(const QString&, bool)));
@ -4058,8 +4086,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data()); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data());
qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue);
scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface); scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface);
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data()); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data());
@ -4267,10 +4293,13 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
QUrl scriptUrl(scriptFilename); QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString(); const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor {
&& !_scriptEnginesHash[scriptURLString]->isFinished()) { QReadLocker lock(&_scriptEnginesHashLock);
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString]; return _scriptEnginesHash[scriptURLString];
}
} }
ScriptEngine* scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); ScriptEngine* scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
@ -4310,7 +4339,11 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) {
void Application::handleScriptEngineLoaded(const QString& scriptFilename) { void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender()); ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
_scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); {
QWriteLocker lock(&_scriptEnginesHashLock);
_scriptEnginesHash.insertMulti(scriptFilename, scriptEngine);
}
_runningScriptsWidget->setRunningScripts(getRunningScripts()); _runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptFilename); UserActivityLogger::getInstance().loadedScript(scriptFilename);
@ -4325,55 +4358,88 @@ void Application::handleScriptLoadError(const QString& scriptFilename) {
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load."); QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
} }
void Application::scriptFinished(const QString& scriptName) { QStringList Application::getRunningScripts() {
const QString& scriptURLString = QUrl(scriptName).toString(); QReadLocker lock(&_scriptEnginesHashLock);
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptURLString); return _scriptEnginesHash.keys();
if (it != _scriptEnginesHash.end()) { }
_scriptEnginesHash.erase(it);
_runningScriptsWidget->scriptStopped(scriptName); ScriptEngine* Application::getScriptEngine(const QString& scriptHash) {
_runningScriptsWidget->setRunningScripts(getRunningScripts()); QReadLocker lock(&_scriptEnginesHashLock);
return _scriptEnginesHash.value(scriptHash, nullptr);
}
void Application::scriptFinished(const QString& scriptName, ScriptEngine* engine) {
bool removed = false;
{
QWriteLocker lock(&_scriptEnginesHashLock);
const QString& scriptURLString = QUrl(scriptName).toString();
for (auto it = _scriptEnginesHash.find(scriptURLString); it != _scriptEnginesHash.end(); ++it) {
if (it.value() == engine) {
_scriptEnginesHash.erase(it);
removed = true;
break;
}
}
}
if (removed) {
postLambdaEvent([this, scriptName]() {
_runningScriptsWidget->scriptStopped(scriptName);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
});
} }
} }
void Application::stopAllScripts(bool restart) { void Application::stopAllScripts(bool restart) {
if (restart) { {
// Delete all running scripts from cache so that they are re-downloaded when they are restarted QReadLocker lock(&_scriptEnginesHashLock);
auto scriptCache = DependencyManager::get<ScriptCache>();
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin(); if (restart) {
// Delete all running scripts from cache so that they are re-downloaded when they are restarted
auto scriptCache = DependencyManager::get<ScriptCache>();
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
it != _scriptEnginesHash.constEnd(); it++) { it != _scriptEnginesHash.constEnd(); it++) {
if (!it.value()->isFinished()) { if (!it.value()->isFinished()) {
scriptCache->deleteScript(it.key()); scriptCache->deleteScript(it.key());
}
} }
} }
}
// Stop and possibly restart all currently running scripts // Stop and possibly restart all currently running scripts
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin(); for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
it != _scriptEnginesHash.constEnd(); it++) { it != _scriptEnginesHash.constEnd(); it++) {
if (it.value()->isFinished()) { if (it.value()->isFinished()) {
continue; continue;
}
if (restart && it.value()->isUserLoaded()) {
connect(it.value(), &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) {
reloadScript(scriptName);
});
}
QMetaObject::invokeMethod(it.value(), "stop");
//it.value()->stop();
qCDebug(interfaceapp) << "stopping script..." << it.key();
} }
if (restart && it.value()->isUserLoaded()) {
connect(it.value(), SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&)));
}
it.value()->stop();
qCDebug(interfaceapp) << "stopping script..." << it.key();
} }
getMyAvatar()->clearScriptableSettings(); getMyAvatar()->clearScriptableSettings();
} }
bool Application::stopScript(const QString& scriptHash, bool restart) { bool Application::stopScript(const QString& scriptHash, bool restart) {
bool stoppedScript = false; bool stoppedScript = false;
if (_scriptEnginesHash.contains(scriptHash)) { {
ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash]; QReadLocker lock(&_scriptEnginesHashLock);
if (restart) { if (_scriptEnginesHash.contains(scriptHash)) {
auto scriptCache = DependencyManager::get<ScriptCache>(); ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash];
scriptCache->deleteScript(QUrl(scriptHash)); if (restart) {
connect(scriptEngine, SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&))); auto scriptCache = DependencyManager::get<ScriptCache>();
scriptCache->deleteScript(QUrl(scriptHash));
connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) {
reloadScript(scriptName);
});
}
scriptEngine->stop();
stoppedScript = true;
qCDebug(interfaceapp) << "stopping script..." << scriptHash;
} }
scriptEngine->stop();
stoppedScript = true;
qCDebug(interfaceapp) << "stopping script..." << scriptHash;
} }
if (_scriptEnginesHash.empty()) { if (_scriptEnginesHash.empty()) {
getMyAvatar()->clearScriptableSettings(); getMyAvatar()->clearScriptableSettings();
@ -4392,6 +4458,7 @@ void Application::reloadOneScript(const QString& scriptName) {
} }
void Application::loadDefaultScripts() { void Application::loadDefaultScripts() {
QReadLocker lock(&_scriptEnginesHashLock);
if (!_scriptEnginesHash.contains(DEFAULT_SCRIPTS_JS_URL)) { if (!_scriptEnginesHash.contains(DEFAULT_SCRIPTS_JS_URL)) {
loadScript(DEFAULT_SCRIPTS_JS_URL); loadScript(DEFAULT_SCRIPTS_JS_URL);
} }

View file

@ -136,7 +136,7 @@ public:
const ViewFrustum* getDisplayViewFrustum() const; const ViewFrustum* getDisplayViewFrustum() const;
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
EntityTreeRenderer* getEntities() { return &_entities; } EntityTreeRenderer* getEntities() { return DependencyManager::get<EntityTreeRenderer>().data(); }
QUndoStack* getUndoStack() { return &_undoStack; } QUndoStack* getUndoStack() { return &_undoStack; }
MainWindow* getWindow() { return _window; } MainWindow* getWindow() { return _window; }
EntityTreePointer getEntityClipboard() { return _entityClipboard; } EntityTreePointer getEntityClipboard() { return _entityClipboard; }
@ -160,14 +160,9 @@ public:
uint32_t getFrameCount() { return _frameCount; } uint32_t getFrameCount() { return _frameCount; }
float getFps() const { return _fps; } float getFps() const { return _fps; }
float const HMD_TARGET_FRAME_RATE = 75.0f; float getTargetFrameRate(); // frames/second
float const DESKTOP_TARGET_FRAME_RATE = 60.0f;
float getTargetFrameRate() { return isHMDMode() ? HMD_TARGET_FRAME_RATE : DESKTOP_TARGET_FRAME_RATE; }
float getTargetFramePeriod() { return isHMDMode() ? 1.0f / HMD_TARGET_FRAME_RATE : 1.0f / DESKTOP_TARGET_FRAME_RATE; } // same as 1/getTargetFrameRate, but w/compile-time division
float getLastInstanteousFps() const { return _lastInstantaneousFps; } float getLastInstanteousFps() const { return _lastInstantaneousFps; }
float getLastPaintWait() const { return _lastPaintWait; }; float getLastUnsynchronizedFps() const { return _lastUnsynchronizedFps; }
float getLastDeducedNonVSyncFps() const { return _lastDeducedNonVSyncFps; }
void setMarginForDeducedFramePeriod(float newValue) { _marginForDeducedFramePeriod = newValue; }
float getFieldOfView() { return _fieldOfView.get(); } float getFieldOfView() { return _fieldOfView.get(); }
void setFieldOfView(float fov); void setFieldOfView(float fov);
@ -202,8 +197,8 @@ public:
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } QStringList getRunningScripts();
ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.value(scriptHash, NULL); } ScriptEngine* getScriptEngine(const QString& scriptHash);
float getRenderResolutionScale() const; float getRenderResolutionScale() const;
@ -336,7 +331,7 @@ private slots:
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();
void scriptFinished(const QString& scriptName); void scriptFinished(const QString& scriptName, ScriptEngine* engine);
void saveScripts(); void saveScripts();
void reloadScript(const QString& scriptName, bool isUserLoaded = true); void reloadScript(const QString& scriptName, bool isUserLoaded = true);
@ -395,6 +390,7 @@ private:
bool importSVOFromURL(const QString& urlString); bool importSVOFromURL(const QString& urlString);
bool nearbyEntitiesAreReadyForPhysics();
int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode); int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode);
void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket); void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket);
@ -442,15 +438,12 @@ private:
QElapsedTimer _timerStart; QElapsedTimer _timerStart;
QElapsedTimer _lastTimeUpdated; QElapsedTimer _lastTimeUpdated;
float _lastInstantaneousFps { 0.0f }; float _lastInstantaneousFps { 0.0f };
float _lastPaintWait { 0.0f }; float _lastUnsynchronizedFps { 0.0f };
float _lastDeducedNonVSyncFps { 0.0f };
float _marginForDeducedFramePeriod{ 0.002f }; // 2ms, adjustable
ShapeManager _shapeManager; ShapeManager _shapeManager;
PhysicalEntitySimulation _entitySimulation; PhysicalEntitySimulation _entitySimulation;
PhysicsEnginePointer _physicsEngine; PhysicsEnginePointer _physicsEngine;
EntityTreeRenderer _entities;
EntityTreeRenderer _entityClipboardRenderer; EntityTreeRenderer _entityClipboardRenderer;
EntityTreePointer _entityClipboard; EntityTreePointer _entityClipboard;
@ -505,6 +498,7 @@ private:
TouchEvent _lastTouchEvent; TouchEvent _lastTouchEvent;
QReadWriteLock _scriptEnginesHashLock;
RunningScriptsWidget* _runningScriptsWidget; RunningScriptsWidget* _runningScriptsWidget;
QHash<QString, ScriptEngine*> _scriptEnginesHash; QHash<QString, ScriptEngine*> _scriptEnginesHash;
bool _runningScriptsWidgetWasVisible; bool _runningScriptsWidgetWasVisible;
@ -564,6 +558,7 @@ private:
bool _isForeground = true; // starts out assumed to be in foreground bool _isForeground = true; // starts out assumed to be in foreground
bool _inPaint = false; bool _inPaint = false;
bool _isGLInitialized { false }; bool _isGLInitialized { false };
bool _physicsEnabled { false };
}; };
#endif // hifi_Application_h #endif // hifi_Application_h

View file

@ -200,7 +200,6 @@ void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("skeleton"); PerformanceTimer perfTimer("skeleton");
_skeletonModel.getRig()->copyJointsFromJointData(_jointData); _skeletonModel.getRig()->copyJointsFromJointData(_jointData);
_skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); _skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations);
simulateAttachments(deltaTime);
locationChanged(); // joints changed, so if there are any children, update them. locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointRotations = false; _hasNewJointRotations = false;
_hasNewJointTranslations = false; _hasNewJointTranslations = false;

View file

@ -69,6 +69,7 @@ public:
void init(); void init();
void simulate(float deltaTime); void simulate(float deltaTime);
void simulateAttachments(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition);
@ -87,7 +88,6 @@ public:
bool isInitialized() const { return _initialized; } bool isInitialized() const { return _initialized; }
SkeletonModel& getSkeletonModel() { return _skeletonModel; } SkeletonModel& getSkeletonModel() { return _skeletonModel; }
const SkeletonModel& getSkeletonModel() const { return _skeletonModel; } const SkeletonModel& getSkeletonModel() const { return _skeletonModel; }
const QVector<Model*>& getAttachmentModels() const { return _attachmentModels; }
glm::vec3 getChestPosition() const; glm::vec3 getChestPosition() const;
float getAvatarScale() const { return getScale().y; } float getAvatarScale() const { return getScale().y; }
const Head* getHead() const { return static_cast<const Head*>(_headData); } const Head* getHead() const { return static_cast<const Head*>(_headData); }
@ -217,8 +217,6 @@ protected:
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
virtual void fixupModelsInScene(); virtual void fixupModelsInScene();
void simulateAttachments(float deltaTime);
virtual void updateJointMappings(); virtual void updateJointMappings();
render::ItemID _renderItemID; render::ItemID _renderItemID;

View file

@ -111,7 +111,6 @@ void AvatarManager::init() {
_renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. _renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0.
_renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP. _renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP.
_renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there. _renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there.
} }
void AvatarManager::updateMyAvatar(float deltaTime) { void AvatarManager::updateMyAvatar(float deltaTime) {
@ -146,13 +145,19 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
PerformanceTimer perfTimer("otherAvatars"); PerformanceTimer perfTimer("otherAvatars");
_renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. float distance;
// The PID controller raises the controlled value when the measured value goes up. if (!qApp->isThrottleRendering()) {
// The measured value is frame rate. When the controlled value (1 / render cutoff distance) _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight.
// goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate // The PID controller raises the controlled value when the measured value goes up.
// goes up. // The measured value is frame rate. When the controlled value (1 / render cutoff distance)
const float deduced = qApp->getLastDeducedNonVSyncFps(); // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate
const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); // goes up.
const float deduced = qApp->getLastUnsynchronizedFps();
distance = 1.0f / _renderDistanceController.update(deduced, deltaTime);
} else {
// Here we choose to just use the maximum render cutoff distance if throttled.
distance = 1.0f / _renderDistanceController.getControlledValueLowLimit();
}
_renderDistanceAverage.updateAverage(distance); _renderDistanceAverage.updateAverage(distance);
_renderDistance = _renderDistanceAverage.getAverage(); _renderDistance = _renderDistanceAverage.getAverage();
int renderableCount = 0; int renderableCount = 0;

View file

@ -323,11 +323,6 @@ void MyAvatar::simulate(float deltaTime) {
return; return;
} }
{
PerformanceTimer perfTimer("attachments");
simulateAttachments(deltaTime);
}
{ {
PerformanceTimer perfTimer("joints"); PerformanceTimer perfTimer("joints");
// copy out the skeleton joints from the model // copy out the skeleton joints from the model
@ -991,6 +986,14 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_headBoneSet.clear(); _headBoneSet.clear();
} }
void MyAvatar::resetFullAvatarURL() {
auto lastAvatarURL = getFullAvatarURLFromPreferences();
auto lastAvatarName = getFullAvatarModelName();
useFullAvatarURL(QUrl());
useFullAvatarURL(lastAvatarURL, lastAvatarName);
}
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
@ -1058,7 +1061,7 @@ void MyAvatar::rebuildSkeletonBody() {
void MyAvatar::prepareForPhysicsSimulation() { void MyAvatar::prepareForPhysicsSimulation() {
relayDriveKeysToCharacterController(); relayDriveKeysToCharacterController();
_characterController.setTargetVelocity(getTargetVelocity()); _characterController.setTargetVelocity(getTargetVelocity());
_characterController.setAvatarPositionAndOrientation(getPosition(), getOrientation()); _characterController.setPositionAndOrientation(getPosition(), getOrientation());
if (qApp->isHMDMode()) { if (qApp->isHMDMode()) {
updateHMDFollowVelocity(); updateHMDFollowVelocity();
} else if (_followSpeed > 0.0f) { } else if (_followSpeed > 0.0f) {
@ -1071,7 +1074,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
void MyAvatar::harvestResultsFromPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation() {
glm::vec3 position = getPosition(); glm::vec3 position = getPosition();
glm::quat orientation = getOrientation(); glm::quat orientation = getOrientation();
_characterController.getAvatarPositionAndOrientation(position, orientation); _characterController.getPositionAndOrientation(position, orientation);
nextAttitude(position, orientation); nextAttitude(position, orientation);
if (_followSpeed > 0.0f) { if (_followSpeed > 0.0f) {
adjustSensorTransform(); adjustSensorTransform();

View file

@ -196,6 +196,8 @@ public:
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; } Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; }
void resetFullAvatarURL();
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override; virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;

View file

@ -11,255 +11,25 @@
#include "MyCharacterController.h" #include "MyCharacterController.h"
#include <BulletCollision/CollisionShapes/btMultiSphereShape.h> #include <BulletUtil.h>
#include <BulletDynamics/Dynamics/btRigidBody.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btDefaultMotionState.h>
#include <GLMHelpers.h>
#include <PhysicsLogging.h>
#include <PhysicsCollisionGroups.h>
#include "MyAvatar.h" #include "MyAvatar.h"
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
const float DEFAULT_GRAVITY = -5.0f;
const float JUMP_SPEED = 3.5f;
const float MAX_FALL_HEIGHT = 20.0f;
// TODO: improve walking up steps // TODO: improve walking up steps
// TODO: make avatars able to walk up and down steps/slopes // TODO: make avatars able to walk up and down steps/slopes
// TODO: make avatars stand on steep slope // TODO: make avatars stand on steep slope
// TODO: make avatars not snag on low ceilings // TODO: make avatars not snag on low ceilings
// helper class for simple ray-traces from character
class ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback {
public:
ClosestNotMe(btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
_me = me;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) {
if (rayResult.m_collisionObject == _me) {
return 1.0f;
}
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
protected:
btRigidBody* _me;
};
MyCharacterController::MyCharacterController(MyAvatar* avatar) { MyCharacterController::MyCharacterController(MyAvatar* avatar) {
_halfHeight = 1.0f;
assert(avatar); assert(avatar);
_avatar = avatar; _avatar = avatar;
_enabled = false;
_floorDistance = MAX_FALL_HEIGHT;
_walkVelocity.setValue(0.0f, 0.0f, 0.0f);
_followVelocity.setValue(0.0f, 0.0f, 0.0f);
_jumpSpeed = JUMP_SPEED;
_isOnGround = false;
_isJumping = false;
_isFalling = false;
_isHovering = true;
_isPushingUp = false;
_jumpToHoverStart = 0;
_followTime = 0.0f;
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
updateShapeIfNecessary(); updateShapeIfNecessary();
} }
MyCharacterController::~MyCharacterController() { MyCharacterController::~MyCharacterController() {
} }
void MyCharacterController::preStep(btCollisionWorld* collisionWorld) {
// trace a ray straight down to see if we're standing on the ground
const btTransform& xform = _rigidBody->getWorldTransform();
// rayStart is at center of bottom sphere
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
// rayEnd is some short distance outside bottom sphere
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
btScalar rayLength = _radius + FLOOR_PROXIMITY_THRESHOLD;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
// scan down for nearby floor
ClosestNotMe rayCallback(_rigidBody);
rayCallback.m_closestHitFraction = 1.0f;
collisionWorld->rayTest(rayStart, rayEnd, rayCallback);
if (rayCallback.hasHit()) {
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
}
}
void MyCharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
btVector3 actualVelocity = _rigidBody->getLinearVelocity();
btScalar actualSpeed = actualVelocity.length();
btVector3 desiredVelocity = _walkVelocity;
btScalar desiredSpeed = desiredVelocity.length();
const btScalar MIN_UP_PUSH = 0.1f;
if (desiredVelocity.dot(_currentUp) < MIN_UP_PUSH) {
_isPushingUp = false;
}
const btScalar MIN_SPEED = 0.001f;
if (_isHovering) {
if (desiredSpeed < MIN_SPEED) {
if (actualSpeed < MIN_SPEED) {
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
} else {
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
btScalar tau = glm::max(dt / HOVER_BRAKING_TIMESCALE, 1.0f);
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
}
} else {
const btScalar HOVER_ACCELERATION_TIMESCALE = 0.1f;
btScalar tau = dt / HOVER_ACCELERATION_TIMESCALE;
_rigidBody->setLinearVelocity(actualVelocity - tau * (actualVelocity - desiredVelocity));
}
} else {
if (onGround()) {
// walking on ground
if (desiredSpeed < MIN_SPEED) {
if (actualSpeed < MIN_SPEED) {
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
} else {
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
btScalar tau = dt / HOVER_BRAKING_TIMESCALE;
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
}
} else {
// TODO: modify desiredVelocity using floor normal
const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f;
btScalar tau = dt / WALK_ACCELERATION_TIMESCALE;
btVector3 velocityCorrection = tau * (desiredVelocity - actualVelocity);
// subtract vertical component
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
_rigidBody->setLinearVelocity(actualVelocity + velocityCorrection);
}
} else {
// transitioning to flying
btVector3 velocityCorrection = desiredVelocity - actualVelocity;
const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f;
btScalar tau = dt / FLY_ACCELERATION_TIMESCALE;
if (!_isPushingUp) {
// actually falling --> compute a different velocity attenuation factor
const btScalar FALL_ACCELERATION_TIMESCALE = 2.0f;
tau = dt / FALL_ACCELERATION_TIMESCALE;
// zero vertical component
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
}
_rigidBody->setLinearVelocity(actualVelocity + tau * velocityCorrection);
}
}
// Rather than add _followVelocity to the velocity of the RigidBody, we explicitly teleport
// the RigidBody forward according to the formula: distance = rate * time
if (_followVelocity.length2() > 0.0f) {
btTransform bodyTransform = _rigidBody->getWorldTransform();
bodyTransform.setOrigin(bodyTransform.getOrigin() + dt * _followVelocity);
_rigidBody->setWorldTransform(bodyTransform);
}
_followTime += dt;
}
void MyCharacterController::jump() {
// check for case where user is holding down "jump" key...
// we'll eventually tansition to "hover"
if (!_isJumping) {
if (!_isHovering) {
_jumpToHoverStart = usecTimestampNow();
_pendingFlags |= PENDING_FLAG_JUMP;
}
} else {
quint64 now = usecTimestampNow();
const quint64 JUMP_TO_HOVER_PERIOD = 75 * (USECS_PER_SECOND / 100);
if (now - _jumpToHoverStart > JUMP_TO_HOVER_PERIOD) {
_isPushingUp = true;
setHovering(true);
}
}
}
bool MyCharacterController::onGround() const {
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
return _floorDistance < FLOOR_PROXIMITY_THRESHOLD;
}
void MyCharacterController::setHovering(bool hover) {
if (hover != _isHovering) {
_isHovering = hover;
_isJumping = false;
if (_rigidBody) {
if (hover) {
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
} else {
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp);
}
}
}
}
void MyCharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
_boxScale = scale;
float x = _boxScale.x;
float z = _boxScale.z;
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
float halfHeight = 0.5f * _boxScale.y - radius;
float MIN_HALF_HEIGHT = 0.1f;
if (halfHeight < MIN_HALF_HEIGHT) {
halfHeight = MIN_HALF_HEIGHT;
}
// compare dimensions
float radiusDelta = glm::abs(radius - _radius);
float heightDelta = glm::abs(halfHeight - _halfHeight);
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
// shape hasn't changed --> nothing to do
} else {
if (_dynamicsWorld) {
// must REMOVE from world prior to shape update
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
}
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
// only need to ADD back when we happen to be enabled
if (_enabled) {
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
}
}
// it's ok to change offset immediately -- there are no thread safety issues here
_shapeLocalOffset = corner + 0.5f * _boxScale;
}
void MyCharacterController::setEnabled(bool enabled) {
if (enabled != _enabled) {
if (enabled) {
// Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit.
// Setting the ADD bit here works for all cases so we don't even bother checking other bits.
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
} else {
if (_dynamicsWorld) {
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
}
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
_isOnGround = false;
}
setHovering(true);
_enabled = enabled;
}
}
void MyCharacterController::updateShapeIfNecessary() { void MyCharacterController::updateShapeIfNecessary() {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE; _pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
@ -300,7 +70,7 @@ void MyCharacterController::updateShapeIfNecessary() {
if (_isHovering) { if (_isHovering) {
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f)); _rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
} else { } else {
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp); _rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
} }
//_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); //_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
} else { } else {
@ -309,98 +79,3 @@ void MyCharacterController::updateShapeIfNecessary() {
} }
} }
void MyCharacterController::updateUpAxis(const glm::quat& rotation) {
btVector3 oldUp = _currentUp;
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
if (!_isHovering) {
const btScalar MIN_UP_ERROR = 0.01f;
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp);
}
}
}
void MyCharacterController::setAvatarPositionAndOrientation(
const glm::vec3& position,
const glm::quat& orientation) {
// TODO: update gravity if up has changed
updateUpAxis(orientation);
btQuaternion bodyOrientation = glmToBullet(orientation);
btVector3 bodyPosition = glmToBullet(position + orientation * _shapeLocalOffset);
_avatarBodyTransform = btTransform(bodyOrientation, bodyPosition);
}
void MyCharacterController::getAvatarPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
if (_enabled && _rigidBody) {
const btTransform& avatarTransform = _rigidBody->getWorldTransform();
rotation = bulletToGLM(avatarTransform.getRotation());
position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset;
}
}
void MyCharacterController::setTargetVelocity(const glm::vec3& velocity) {
//_walkVelocity = glmToBullet(_avatarData->getTargetVelocity());
_walkVelocity = glmToBullet(velocity);
}
void MyCharacterController::setFollowVelocity(const glm::vec3& velocity) {
_followVelocity = glmToBullet(velocity);
}
glm::vec3 MyCharacterController::getLinearVelocity() const {
glm::vec3 velocity(0.0f);
if (_rigidBody) {
velocity = bulletToGLM(_rigidBody->getLinearVelocity());
}
return velocity;
}
void MyCharacterController::preSimulation() {
if (_enabled && _dynamicsWorld) {
// slam body to where it is supposed to be
_rigidBody->setWorldTransform(_avatarBodyTransform);
// scan for distant floor
// rayStart is at center of bottom sphere
btVector3 rayStart = _avatarBodyTransform.getOrigin() - _halfHeight * _currentUp;
// rayEnd is straight down MAX_FALL_HEIGHT
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
ClosestNotMe rayCallback(_rigidBody);
rayCallback.m_closestHitFraction = 1.0f;
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
if (rayCallback.hasHit()) {
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
const btScalar MIN_HOVER_HEIGHT = 3.0f;
if (_isHovering && _floorDistance < MIN_HOVER_HEIGHT && !_isPushingUp) {
setHovering(false);
}
// TODO: use collision events rather than ray-trace test to disable jumping
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
if (_floorDistance < JUMP_PROXIMITY_THRESHOLD) {
_isJumping = false;
}
} else {
_floorDistance = FLT_MAX;
setHovering(true);
}
if (_pendingFlags & PENDING_FLAG_JUMP) {
_pendingFlags &= ~ PENDING_FLAG_JUMP;
if (onGround()) {
_isJumping = true;
btVector3 velocity = _rigidBody->getLinearVelocity();
velocity += _jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
}
}
_followTime = 0.0f;
}
void MyCharacterController::postSimulation() {
// postSimulation() exists for symmetry and just in case we need to do something here later
}

View file

@ -13,12 +13,8 @@
#ifndef hifi_MyCharacterController_h #ifndef hifi_MyCharacterController_h
#define hifi_MyCharacterController_h #define hifi_MyCharacterController_h
#include <btBulletDynamicsCommon.h>
#include <glm/glm.hpp>
#include <BulletUtil.h>
#include <CharacterController.h> #include <CharacterController.h>
#include <SharedUtil.h> //#include <SharedUtil.h>
class btCollisionShape; class btCollisionShape;
class MyAvatar; class MyAvatar;
@ -28,79 +24,10 @@ public:
MyCharacterController(MyAvatar* avatar); MyCharacterController(MyAvatar* avatar);
~MyCharacterController (); ~MyCharacterController ();
// TODO: implement these when needed
virtual void setVelocityForTimeInterval(const btVector3 &velocity, btScalar timeInterval) override { assert(false); }
virtual void reset(btCollisionWorld* collisionWorld) override { }
virtual void warp(const btVector3& origin) override { }
virtual void debugDraw(btIDebugDraw* debugDrawer) override { }
virtual void setUpInterpolate(bool value) override { }
// overrides from btCharacterControllerInterface
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override {
preStep(collisionWorld);
playerStep(collisionWorld, deltaTime);
}
virtual void preStep(btCollisionWorld* collisionWorld) override;
virtual void playerStep(btCollisionWorld* collisionWorld, btScalar dt) override;
virtual bool canJump() const override { assert(false); return false; } // never call this
virtual void jump() override; // call this every frame the jump button is pressed
virtual bool onGround() const override;
// overrides from CharacterController
virtual void preSimulation() override;
virtual void postSimulation() override;
bool isHovering() const { return _isHovering; }
void setHovering(bool enabled);
void setEnabled(bool enabled);
bool isEnabled() const { return _enabled && _dynamicsWorld; }
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
virtual void updateShapeIfNecessary() override; virtual void updateShapeIfNecessary() override;
void setAvatarPositionAndOrientation( const glm::vec3& position, const glm::quat& orientation);
void getAvatarPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
void setTargetVelocity(const glm::vec3& velocity);
void setFollowVelocity(const glm::vec3& velocity);
float getFollowTime() const { return _followTime; }
glm::vec3 getLinearVelocity() const;
protected: protected:
void updateUpAxis(const glm::quat& rotation);
protected:
btVector3 _currentUp;
btVector3 _walkVelocity;
btVector3 _followVelocity;
btTransform _avatarBodyTransform;
glm::vec3 _shapeLocalOffset;
glm::vec3 _boxScale; // used to compute capsule shape
quint64 _jumpToHoverStart;
MyAvatar* _avatar { nullptr }; MyAvatar* _avatar { nullptr };
btScalar _halfHeight;
btScalar _radius;
btScalar _floorDistance;
btScalar _gravity;
btScalar _jumpSpeed;
btScalar _followTime;
bool _enabled;
bool _isOnGround;
bool _isJumping;
bool _isFalling;
bool _isHovering;
bool _isPushingUp;
}; };
#endif // hifi_MyCharacterController_h #endif // hifi_MyCharacterController_h

View file

@ -20,9 +20,6 @@
#include "Application.h" #include "Application.h"
#include "devices/MotionTracker.h" #include "devices/MotionTracker.h"
// TODO: this needs to be removed, as well as any related controller-specific information
#include <input-plugins/SixenseManager.h>
void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) { void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) {
if (event->type() == HFActionEvent::startType()) { if (event->type() == HFActionEvent::startType()) {
emit actionStartEvent(static_cast<HFActionEvent&>(*event)); emit actionStartEvent(static_cast<HFActionEvent&>(*event));

View file

@ -26,7 +26,6 @@
#include "Tooltip.h" #include "Tooltip.h"
#include "Application.h" #include "Application.h"
#include <input-plugins/SixenseManager.h> // TODO: any references to sixense should be removed here
#include <controllers/InputDevice.h> #include <controllers/InputDevice.h>

View file

@ -16,7 +16,6 @@
#include <avatar/AvatarManager.h> #include <avatar/AvatarManager.h>
#include <devices/DdeFaceTracker.h> #include <devices/DdeFaceTracker.h>
#include <devices/Faceshift.h> #include <devices/Faceshift.h>
#include <input-plugins/SixenseManager.h> // TODO: This should be replaced with InputDevice/InputPlugin, or something similar
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
#include "Application.h" #include "Application.h"

View file

@ -50,9 +50,11 @@ QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine,
if (useNames) { // copy only the requested names if (useNames) { // copy only the requested names
for (const QString& name : names) { for (const QString& name : names) {
auto search = _map.find(name); auto search = _map.find(name);
if (search != _map.end()) { // scripts are allowed to request names that do not exist if (search != _map.end()) {
setOne(name, search->second); setOne(name, search->second);
} } else if (_triggers.count(name) == 1) {
target.setProperty(name, true);
} // scripts are allowed to request names that do not exist
} }
} else { // copy all of them } else { // copy all of them

View file

@ -717,7 +717,8 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh
// This works (I tried it), but the result would be that we would still have same runtime type checks as the invokeMethod above // This works (I tried it), but the result would be that we would still have same runtime type checks as the invokeMethod above
// (occuring within the ScriptEngine::callAnimationStateHandler invokeMethod trampoline), _plus_ another runtime check for the dynamic_cast. // (occuring within the ScriptEngine::callAnimationStateHandler invokeMethod trampoline), _plus_ another runtime check for the dynamic_cast.
// gather results in (likely from an earlier update): // Gather results in (likely from an earlier update).
// Note: the behavior is undefined if a handler (re-)sets a trigger. Scripts should not be doing that.
_animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registgered wins. (_map preserves order). _animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registgered wins. (_map preserves order).
} }
} }

View file

@ -541,26 +541,40 @@ bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) {
void AudioClient::configureReverb() { void AudioClient::configureReverb() {
ReverbParameters p; ReverbParameters p;
_listenerReverb.getParameters(&p);
// for now, reuse the gverb parameters
p.sampleRate = _outputFormat.sampleRate(); p.sampleRate = _outputFormat.sampleRate();
p.roomSize = _reverbOptions->getRoomSize();
p.bandwidth = _reverbOptions->getBandwidth();
p.preDelay = _reverbOptions->getPreDelay();
p.lateDelay = _reverbOptions->getLateDelay();
p.reverbTime = _reverbOptions->getReverbTime(); p.reverbTime = _reverbOptions->getReverbTime();
p.highGain = -24.0f * (1.0f - _reverbOptions->getDamping()); p.earlyDiffusion = _reverbOptions->getEarlyDiffusion();
p.bandwidth = 10000.0f * _reverbOptions->getInputBandwidth(); p.lateDiffusion = _reverbOptions->getLateDiffusion();
p.earlyGain = _reverbOptions->getEarlyLevel(); p.roomSize = _reverbOptions->getRoomSize();
p.lateGain = _reverbOptions->getTailLevel(); p.density = _reverbOptions->getDensity();
p.wetDryMix = 100.0f * powf(10.0f, _reverbOptions->getWetLevel() * (1/20.0f)); p.bassMult = _reverbOptions->getBassMult();
p.bassFreq = _reverbOptions->getBassFreq();
p.highGain = _reverbOptions->getHighGain();
p.highFreq = _reverbOptions->getHighFreq();
p.modRate = _reverbOptions->getModRate();
p.modDepth = _reverbOptions->getModDepth();
p.earlyGain = _reverbOptions->getEarlyGain();
p.lateGain = _reverbOptions->getLateGain();
p.earlyMixLeft = _reverbOptions->getEarlyMixLeft();
p.earlyMixRight = _reverbOptions->getEarlyMixRight();
p.lateMixLeft = _reverbOptions->getLateMixLeft();
p.lateMixRight = _reverbOptions->getLateMixRight();
p.wetDryMix = _reverbOptions->getWetDryMix();
_listenerReverb.setParameters(&p); _listenerReverb.setParameters(&p);
// used for adding self-reverb to loopback audio // used only for adding self-reverb to loopback audio
p.wetDryMix = 100.0f; p.wetDryMix = 100.0f;
p.preDelay = 0.0f; p.preDelay = 0.0f;
p.earlyGain = -96.0f; // disable ER p.earlyGain = -96.0f; // disable ER
p.lateGain -= 12.0f; // quieter than listener reverb p.lateGain -= 12.0f; // quieter than listener reverb
p.lateMixLeft = 0.0f; p.lateMixLeft = 0.0f;
p.lateMixRight = 0.0f; p.lateMixRight = 0.0f;
_sourceReverb.setParameters(&p); _sourceReverb.setParameters(&p);
} }
@ -572,10 +586,10 @@ void AudioClient::updateReverbOptions() {
_zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime());
reverbChanged = true; reverbChanged = true;
} }
if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { //if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) {
_zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); // _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel());
reverbChanged = true; // reverbChanged = true;
} //}
if (_reverbOptions != &_zoneReverbOptions) { if (_reverbOptions != &_zoneReverbOptions) {
_reverbOptions = &_zoneReverbOptions; _reverbOptions = &_zoneReverbOptions;
@ -602,17 +616,27 @@ void AudioClient::setReverb(bool reverb) {
void AudioClient::setReverbOptions(const AudioEffectOptions* options) { void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
// Save the new options // Save the new options
_scriptReverbOptions.setMaxRoomSize(options->getMaxRoomSize()); _scriptReverbOptions.setBandwidth(options->getBandwidth());
_scriptReverbOptions.setRoomSize(options->getRoomSize()); _scriptReverbOptions.setPreDelay(options->getPreDelay());
_scriptReverbOptions.setLateDelay(options->getLateDelay());
_scriptReverbOptions.setReverbTime(options->getReverbTime()); _scriptReverbOptions.setReverbTime(options->getReverbTime());
_scriptReverbOptions.setDamping(options->getDamping()); _scriptReverbOptions.setEarlyDiffusion(options->getEarlyDiffusion());
_scriptReverbOptions.setSpread(options->getSpread()); _scriptReverbOptions.setLateDiffusion(options->getLateDiffusion());
_scriptReverbOptions.setInputBandwidth(options->getInputBandwidth()); _scriptReverbOptions.setRoomSize(options->getRoomSize());
_scriptReverbOptions.setEarlyLevel(options->getEarlyLevel()); _scriptReverbOptions.setDensity(options->getDensity());
_scriptReverbOptions.setTailLevel(options->getTailLevel()); _scriptReverbOptions.setBassMult(options->getBassMult());
_scriptReverbOptions.setBassFreq(options->getBassFreq());
_scriptReverbOptions.setDryLevel(options->getDryLevel()); _scriptReverbOptions.setHighGain(options->getHighGain());
_scriptReverbOptions.setWetLevel(options->getWetLevel()); _scriptReverbOptions.setHighFreq(options->getHighFreq());
_scriptReverbOptions.setModRate(options->getModRate());
_scriptReverbOptions.setModDepth(options->getModDepth());
_scriptReverbOptions.setEarlyGain(options->getEarlyGain());
_scriptReverbOptions.setLateGain(options->getLateGain());
_scriptReverbOptions.setEarlyMixLeft(options->getEarlyMixLeft());
_scriptReverbOptions.setEarlyMixRight(options->getEarlyMixRight());
_scriptReverbOptions.setLateMixLeft(options->getLateMixLeft());
_scriptReverbOptions.setLateMixRight(options->getLateMixRight());
_scriptReverbOptions.setWetDryMix(options->getWetDryMix());
if (_reverbOptions == &_scriptReverbOptions) { if (_reverbOptions == &_scriptReverbOptions) {
// Apply them to the reverb instances // Apply them to the reverb instances

View file

@ -10,58 +10,76 @@
#include "AudioEffectOptions.h" #include "AudioEffectOptions.h"
static const QString MAX_ROOM_SIZE_HANDLE = "maxRoomSize"; static const QString BANDWIDTH_HANDLE = "bandwidth";
static const QString ROOM_SIZE_HANDLE = "roomSize"; static const QString PRE_DELAY_HANDLE = "preDelay";
static const QString LATE_DELAY_HANDLE = "lateDelay";
static const QString REVERB_TIME_HANDLE = "reverbTime"; static const QString REVERB_TIME_HANDLE = "reverbTime";
static const QString DAMPIMG_HANDLE = "damping"; static const QString EARLY_DIFFUSION_HANDLE = "earlyDiffusion";
static const QString SPREAD_HANDLE = "spread"; static const QString LATE_DIFFUSION_HANDLE = "lateDiffusion";
static const QString INPUT_BANDWIDTH_HANDLE = "inputBandwidth"; static const QString ROOM_SIZE_HANDLE = "roomSize";
static const QString EARLY_LEVEL_HANDLE = "earlyLevel"; static const QString DENSITY_HANDLE = "density";
static const QString TAIL_LEVEL_HANDLE = "tailLevel"; static const QString BASS_MULT_HANDLE = "bassMult";
static const QString DRY_LEVEL_HANDLE = "dryLevel"; static const QString BASS_FREQ_HANDLE = "bassFreq";
static const QString WET_LEVEL_HANDLE = "wetLevel"; static const QString HIGH_GAIN_HANDLE = "highGain";
static const QString HIGH_FREQ_HANDLE = "highFreq";
static const QString MOD_RATE_HANDLE = "modRate";
static const QString MOD_DEPTH_HANDLE = "modDepth";
static const QString EARLY_GAIN_HANDLE = "earlyGain";
static const QString LATE_GAIN_HANDLE = "lateGain";
static const QString EARLY_MIX_LEFT_HANDLE = "earlyMixLeft";
static const QString EARLY_MIX_RIGHT_HANDLE = "earlyMixRight";
static const QString LATE_MIX_LEFT_HANDLE = "lateMixLeft";
static const QString LATE_MIX_RIGHT_HANDLE = "lateMixRight";
static const QString WET_DRY_MIX_HANDLE = "wetDryMix";
AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) : static const float BANDWIDTH_DEFAULT = 10000.0f;
_maxRoomSize(50.0f), static const float PRE_DELAY_DEFAULT = 20.0f;
_roomSize(50.0f), static const float LATE_DELAY_DEFAULT = 0.0f;
_reverbTime(4.0f), static const float REVERB_TIME_DEFAULT = 2.0f;
_damping(0.5f), static const float EARLY_DIFFUSION_DEFAULT = 100.0f;
_spread(15.0f), static const float LATE_DIFFUSION_DEFAULT = 100.0f;
_inputBandwidth(0.75f), static const float ROOM_SIZE_DEFAULT = 50.0f;
_earlyLevel(-12.0f), static const float DENSITY_DEFAULT = 100.0f;
_tailLevel(-18.0f), static const float BASS_MULT_DEFAULT = 1.5f;
_dryLevel(0.0f), static const float BASS_FREQ_DEFAULT = 250.0f;
_wetLevel(0.0f) { static const float HIGH_GAIN_DEFAULT = -6.0f;
if (arguments.property(MAX_ROOM_SIZE_HANDLE).isNumber()) { static const float HIGH_FREQ_DEFAULT = 3000.0f;
_maxRoomSize = arguments.property(MAX_ROOM_SIZE_HANDLE).toNumber(); static const float MOD_RATE_DEFAULT = 2.3f;
} static const float MOD_DEPTH_DEFAULT = 50.0f;
if (arguments.property(ROOM_SIZE_HANDLE).isNumber()) { static const float EARLY_GAIN_DEFAULT = 0.0f;
_roomSize = arguments.property(ROOM_SIZE_HANDLE).toNumber(); static const float LATE_GAIN_DEFAULT = 0.0f;
} static const float EARLY_MIX_LEFT_DEFAULT = 20.0f;
if (arguments.property(REVERB_TIME_HANDLE).isNumber()) { static const float EARLY_MIX_RIGHT_DEFAULT = 20.0f;
_reverbTime = arguments.property(REVERB_TIME_HANDLE).toNumber(); static const float LATE_MIX_LEFT_DEFAULT = 90.0f;
} static const float LATE_MIX_RIGHT_DEFAULT = 90.0f;
if (arguments.property(DAMPIMG_HANDLE).isNumber()) { static const float WET_DRY_MIX_DEFAULT = 50.0f;
_damping = arguments.property(DAMPIMG_HANDLE).toNumber();
} static void setOption(QScriptValue arguments, const QString name, float defaultValue, float& variable) {
if (arguments.property(SPREAD_HANDLE).isNumber()) { variable = arguments.property(name).isNumber() ? arguments.property(name).toNumber() : defaultValue;
_spread = arguments.property(SPREAD_HANDLE).toNumber(); }
}
if (arguments.property(INPUT_BANDWIDTH_HANDLE).isNumber()) { AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) {
_inputBandwidth = arguments.property(INPUT_BANDWIDTH_HANDLE).toNumber(); setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth);
} setOption(arguments, PRE_DELAY_HANDLE, PRE_DELAY_DEFAULT, _preDelay);
if (arguments.property(EARLY_LEVEL_HANDLE).isNumber()) { setOption(arguments, LATE_DELAY_HANDLE, LATE_DELAY_DEFAULT, _lateDelay);
_earlyLevel = arguments.property(EARLY_LEVEL_HANDLE).toNumber(); setOption(arguments, REVERB_TIME_HANDLE, REVERB_TIME_DEFAULT, _reverbTime);
} setOption(arguments, EARLY_DIFFUSION_HANDLE, EARLY_DIFFUSION_DEFAULT, _earlyDiffusion);
if (arguments.property(TAIL_LEVEL_HANDLE).isNumber()) { setOption(arguments, LATE_DIFFUSION_HANDLE, LATE_DIFFUSION_DEFAULT, _lateDiffusion);
_tailLevel = arguments.property(TAIL_LEVEL_HANDLE).toNumber(); setOption(arguments, ROOM_SIZE_HANDLE, ROOM_SIZE_DEFAULT, _roomSize);
} setOption(arguments, DENSITY_HANDLE, DENSITY_DEFAULT, _density);
if (arguments.property(DRY_LEVEL_HANDLE).isNumber()) { setOption(arguments, BASS_MULT_HANDLE, BASS_MULT_DEFAULT, _bassMult);
_dryLevel = arguments.property(DRY_LEVEL_HANDLE).toNumber(); setOption(arguments, BASS_FREQ_HANDLE, BASS_FREQ_DEFAULT, _bassFreq);
} setOption(arguments, HIGH_GAIN_HANDLE, HIGH_GAIN_DEFAULT, _highGain);
if (arguments.property(WET_LEVEL_HANDLE).isNumber()) { setOption(arguments, HIGH_FREQ_HANDLE, HIGH_FREQ_DEFAULT, _highFreq);
_wetLevel = arguments.property(WET_LEVEL_HANDLE).toNumber(); setOption(arguments, MOD_RATE_HANDLE, MOD_RATE_DEFAULT, _modRate);
} setOption(arguments, MOD_DEPTH_HANDLE, MOD_DEPTH_DEFAULT, _modDepth);
setOption(arguments, EARLY_GAIN_HANDLE, EARLY_GAIN_DEFAULT, _earlyGain);
setOption(arguments, LATE_GAIN_HANDLE, LATE_GAIN_DEFAULT, _lateGain);
setOption(arguments, EARLY_MIX_LEFT_HANDLE, EARLY_MIX_LEFT_DEFAULT, _earlyMixLeft);
setOption(arguments, EARLY_MIX_RIGHT_HANDLE, EARLY_MIX_RIGHT_DEFAULT, _earlyMixRight);
setOption(arguments, LATE_MIX_LEFT_HANDLE, LATE_MIX_LEFT_DEFAULT, _lateMixLeft);
setOption(arguments, LATE_MIX_RIGHT_HANDLE, LATE_MIX_RIGHT_DEFAULT, _lateMixRight);
setOption(arguments, WET_DRY_MIX_HANDLE, WET_DRY_MIX_DEFAULT, _wetDryMix);
} }
AudioEffectOptions::AudioEffectOptions(const AudioEffectOptions &other) : QObject() { AudioEffectOptions::AudioEffectOptions(const AudioEffectOptions &other) : QObject() {
@ -69,17 +87,28 @@ AudioEffectOptions::AudioEffectOptions(const AudioEffectOptions &other) : QObjec
} }
AudioEffectOptions& AudioEffectOptions::operator=(const AudioEffectOptions &other) { AudioEffectOptions& AudioEffectOptions::operator=(const AudioEffectOptions &other) {
_maxRoomSize = other._maxRoomSize; _bandwidth = other._bandwidth;
_roomSize = other._roomSize; _preDelay = other._preDelay;
_lateDelay = other._lateDelay;
_reverbTime = other._reverbTime; _reverbTime = other._reverbTime;
_damping = other._damping; _earlyDiffusion = other._earlyDiffusion;
_spread = other._spread; _lateDiffusion = other._lateDiffusion;
_inputBandwidth = other._inputBandwidth; _roomSize = other._roomSize;
_earlyLevel = other._earlyLevel; _density = other._density;
_tailLevel = other._tailLevel; _bassMult = other._bassMult;
_dryLevel = other._dryLevel; _bassFreq = other._bassFreq;
_wetLevel = other._wetLevel; _highGain = other._highGain;
_highFreq = other._highFreq;
_modRate = other._modRate;
_modDepth = other._modDepth;
_earlyGain = other._earlyGain;
_lateGain = other._lateGain;
_earlyMixLeft = other._earlyMixLeft;
_earlyMixRight = other._earlyMixRight;
_lateMixLeft = other._lateMixLeft;
_lateMixRight = other._lateMixRight;
_wetDryMix = other._wetDryMix;
return *this; return *this;
} }

View file

@ -15,32 +15,30 @@
#include <QtScript/QScriptContext> #include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
#include "AudioReverb.h"
class AudioEffectOptions : public QObject { class AudioEffectOptions : public QObject {
Q_OBJECT Q_OBJECT
// Meters Square Q_PROPERTY(float bandwidth READ getBandwidth WRITE setBandwidth)
Q_PROPERTY(float maxRoomSize READ getMaxRoomSize WRITE setMaxRoomSize) Q_PROPERTY(float preDelay READ getPreDelay WRITE setPreDelay)
Q_PROPERTY(float roomSize READ getRoomSize WRITE setRoomSize) Q_PROPERTY(float lateDelay READ getLateDelay WRITE setLateDelay)
// Seconds
Q_PROPERTY(float reverbTime READ getReverbTime WRITE setReverbTime) Q_PROPERTY(float reverbTime READ getReverbTime WRITE setReverbTime)
Q_PROPERTY(float earlyDiffusion READ getEarlyDiffusion WRITE setEarlyDiffusion)
// Ratio between 0 and 1 Q_PROPERTY(float lateDiffusion READ getLateDiffusion WRITE setLateDiffusion)
Q_PROPERTY(float damping READ getDamping WRITE setDamping) Q_PROPERTY(float roomSize READ getRoomSize WRITE setRoomSize)
Q_PROPERTY(float density READ getDensity WRITE setDensity)
// (?) Does not appear to be set externally very often Q_PROPERTY(float bassMult READ getBassMult WRITE setBassMult)
Q_PROPERTY(float spread READ getSpread WRITE setSpread) Q_PROPERTY(float bassFreq READ getBassFreq WRITE setBassFreq)
Q_PROPERTY(float highGain READ getHighGain WRITE setHighGain)
// Ratio between 0 and 1 Q_PROPERTY(float highFreq READ getHighFreq WRITE setHighFreq)
Q_PROPERTY(float inputBandwidth READ getInputBandwidth WRITE setInputBandwidth) Q_PROPERTY(float modRate READ getModRate WRITE setModRate)
Q_PROPERTY(float modDepth READ getModDepth WRITE setModDepth)
// in dB Q_PROPERTY(float earlyGain READ getEarlyGain WRITE setEarlyGain)
Q_PROPERTY(float earlyLevel READ getEarlyLevel WRITE setEarlyLevel) Q_PROPERTY(float lateGain READ getLateGain WRITE setLateGain)
Q_PROPERTY(float tailLevel READ getTailLevel WRITE setTailLevel) Q_PROPERTY(float earlyMixLeft READ getEarlyMixLeft WRITE setEarlyMixLeft)
Q_PROPERTY(float dryLevel READ getDryLevel WRITE setDryLevel) Q_PROPERTY(float earlyMixRight READ getEarlyMixRight WRITE setEarlyMixRight)
Q_PROPERTY(float wetLevel READ getWetLevel WRITE setWetLevel) Q_PROPERTY(float lateMixLeft READ getLateMixLeft WRITE setLateMixLeft)
Q_PROPERTY(float lateMixRight READ getLateMixRight WRITE setLateMixRight)
Q_PROPERTY(float wetDryMix READ getWetDryMix WRITE setWetDryMix)
public: public:
AudioEffectOptions(QScriptValue arguments = QScriptValue()); AudioEffectOptions(QScriptValue arguments = QScriptValue());
@ -49,60 +47,100 @@ public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
float getRoomSize() const { return _roomSize; } float getBandwidth() const { return _bandwidth; }
void setRoomSize(float roomSize ) { _roomSize = roomSize; } void setBandwidth(float bandwidth) { _bandwidth = bandwidth; }
float getMaxRoomSize() const { return _maxRoomSize; } float getPreDelay() const { return _preDelay; }
void setMaxRoomSize(float maxRoomSize ) { _maxRoomSize = maxRoomSize; } void setPreDelay(float preDelay) { _preDelay = preDelay; }
float getLateDelay() const { return _lateDelay; }
void setLateDelay(float lateDelay) { _lateDelay = lateDelay; }
float getReverbTime() const { return _reverbTime; } float getReverbTime() const { return _reverbTime; }
void setReverbTime(float reverbTime ) { _reverbTime = reverbTime; } void setReverbTime(float reverbTime) { _reverbTime = reverbTime; }
float getDamping() const { return _damping; } float getEarlyDiffusion() const { return _earlyDiffusion; }
void setDamping(float damping ) { _damping = damping; } void setEarlyDiffusion(float earlyDiffusion) { _earlyDiffusion = earlyDiffusion; }
float getSpread() const { return _spread; } float getLateDiffusion() const { return _lateDiffusion; }
void setSpread(float spread ) { _spread = spread; } void setLateDiffusion(float lateDiffusion) { _lateDiffusion = lateDiffusion; }
float getInputBandwidth() const { return _inputBandwidth; } float getRoomSize() const { return _roomSize; }
void setInputBandwidth(float inputBandwidth ) { _inputBandwidth = inputBandwidth; } void setRoomSize(float roomSize) { _roomSize = roomSize; }
float getEarlyLevel() const { return _earlyLevel; } float getDensity() const { return _density; }
void setEarlyLevel(float earlyLevel ) { _earlyLevel = earlyLevel; } void setDensity(float density) { _density = density; }
float getTailLevel() const { return _tailLevel; } float getBassMult() const { return _bassMult; }
void setTailLevel(float tailLevel ) { _tailLevel = tailLevel; } void setBassMult(float bassMult) { _bassMult = bassMult; }
float getDryLevel() const { return _dryLevel; } float getBassFreq() const { return _bassFreq; }
void setDryLevel(float dryLevel) { _dryLevel = dryLevel; } void setBassFreq(float bassFreq) { _bassFreq = bassFreq; }
float getWetLevel() const { return _wetLevel; } float getHighGain() const { return _highGain; }
void setWetLevel(float wetLevel) { _wetLevel = wetLevel; } void setHighGain(float highGain) { _highGain = highGain; }
float getHighFreq() const { return _highFreq; }
void setHighFreq(float highFreq) { _highFreq = highFreq; }
float getModRate() const { return _modRate; }
void setModRate(float modRate) { _modRate = modRate; }
float getModDepth() const { return _modDepth; }
void setModDepth(float modDepth) { _modDepth = modDepth; }
float getEarlyGain() const { return _earlyGain; }
void setEarlyGain(float earlyGain) { _earlyGain = earlyGain; }
float getLateGain() const { return _lateGain; }
void setLateGain(float lateGain) { _lateGain = lateGain; }
float getEarlyMixLeft() const { return _earlyMixLeft; }
void setEarlyMixLeft(float earlyMixLeft) { _earlyMixLeft = earlyMixLeft; }
float getEarlyMixRight() const { return _earlyMixRight; }
void setEarlyMixRight(float earlyMixRight) { _earlyMixRight = earlyMixRight; }
float getLateMixLeft() const { return _lateMixLeft; }
void setLateMixLeft(float lateMixLeft) { _lateMixLeft = lateMixLeft; }
float getLateMixRight() const { return _lateMixRight; }
void setLateMixRight(float lateMixRight) { _lateMixRight = lateMixRight; }
float getWetDryMix() const { return _wetDryMix; }
void setWetDryMix(float wetDryMix) { _wetDryMix = wetDryMix; }
private: private:
// http://wiki.audacityteam.org/wiki/GVerb#Instant_Reverberb_settings float _bandwidth; // [20, 24000] Hz
// Meters Square float _preDelay; // [0, 333] ms
float _maxRoomSize; float _lateDelay; // [0, 166] ms
float _roomSize;
// Seconds float _reverbTime; // [0.1, 100] seconds
float _reverbTime;
// Ratio between 0 and 1 float _earlyDiffusion; // [0, 100] percent
float _damping; float _lateDiffusion; // [0, 100] percent
// ? (Does not appear to be set externally very often) float _roomSize; // [0, 100] percent
float _spread; float _density; // [0, 100] percent
// Ratio between 0 and 1 float _bassMult; // [0.1, 10] ratio
float _inputBandwidth; float _bassFreq; // [10, 500] Hz
float _highGain; // [-24, 0] dB
float _highFreq; // [1000, 12000] Hz
// dB float _modRate; // [0.1, 10] Hz
float _earlyLevel; float _modDepth; // [0, 100] percent
float _tailLevel;
float _dryLevel; float _earlyGain; // [-96, +24] dB
float _wetLevel; float _lateGain; // [-96, +24] dB
float _earlyMixLeft; // [0, 100] percent
float _earlyMixRight; // [0, 100] percent
float _lateMixLeft; // [0, 100] percent
float _lateMixRight; // [0, 100] percent
float _wetDryMix; // [0, 100] percent
}; };
#endif // hifi_AudioEffectOptions_h #endif // hifi_AudioEffectOptions_h

View file

@ -47,7 +47,7 @@ const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData";
static std::once_flag frameTypeRegistration; static std::once_flag frameTypeRegistration;
AvatarData::AvatarData() : AvatarData::AvatarData() :
SpatiallyNestable(NestableTypes::Avatar, QUuid()), SpatiallyNestable(NestableType::Avatar, QUuid()),
_handPosition(0.0f), _handPosition(0.0f),
_targetScale(1.0f), _targetScale(1.0f),
_handState(0), _handState(0),

View file

@ -83,19 +83,18 @@ void Basic2DWindowOpenGLDisplayPlugin::internalPresent() {
} }
WindowOpenGLDisplayPlugin::internalPresent(); WindowOpenGLDisplayPlugin::internalPresent();
} }
const uint32_t THROTTLED_FRAMERATE = 15;
int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const {
static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15;
static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1; static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1;
int result = ULIMIITED_PAINT_TIMER_DELAY_MS; int result = ULIMIITED_PAINT_TIMER_DELAY_MS;
if (_isThrottled) {
result = THROTTLED_PAINT_TIMER_DELAY_MS;
}
if (0 != _framerateTarget) { if (0 != _framerateTarget) {
result = MSECS_PER_SECOND / _framerateTarget; result = MSECS_PER_SECOND / _framerateTarget;
} else if (_isThrottled) {
// This test wouldn't be necessary if we could depend on updateFramerate setting _framerateTarget.
// Alas, that gets complicated: isThrottled() is const and other stuff depends on it.
result = MSECS_PER_SECOND / THROTTLED_FRAMERATE;
} }
qDebug() << "New interval " << result;
return result; return result;
} }
@ -112,7 +111,6 @@ bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const {
return shouldThrottle; return shouldThrottle;
} }
void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() { void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() {
QAction* checkedFramerate{ nullptr }; QAction* checkedFramerate{ nullptr };
foreach(auto action, _framerateActions) { foreach(auto action, _framerateActions) {
@ -134,11 +132,12 @@ void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() {
} else if (FRAMERATE_30 == actionText) { } else if (FRAMERATE_30 == actionText) {
_framerateTarget = 30; _framerateTarget = 30;
} }
} } else if (_isThrottled) {
_framerateTarget = THROTTLED_FRAMERATE;
}
int newInterval = getDesiredInterval(); int newInterval = getDesiredInterval();
qDebug() << newInterval; _timer.start(newInterval);
_timer.start(getDesiredInterval());
} }
// FIXME target the screen the window is currently on // FIXME target the screen the window is currently on

View file

@ -9,6 +9,8 @@
#include "WindowOpenGLDisplayPlugin.h" #include "WindowOpenGLDisplayPlugin.h"
const float TARGET_FRAMERATE_Basic2DWindowOpenGL = 60.0f;
class QScreen; class QScreen;
class QAction; class QAction;
@ -18,6 +20,8 @@ class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin {
public: public:
virtual const QString & getName() const override; virtual const QString & getName() const override;
virtual float getTargetFrameRate() override { return _framerateTarget ? (float) _framerateTarget : TARGET_FRAMERATE_Basic2DWindowOpenGL; }
virtual void activate() override; virtual void activate() override;
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;

View file

@ -14,8 +14,6 @@
#include "stereo/InterleavedStereoDisplayPlugin.h" #include "stereo/InterleavedStereoDisplayPlugin.h"
#include "Basic2DWindowOpenGLDisplayPlugin.h" #include "Basic2DWindowOpenGLDisplayPlugin.h"
#include "openvr/OpenVrDisplayPlugin.h"
const QString& DisplayPlugin::MENU_PATH() { const QString& DisplayPlugin::MENU_PATH() {
static const QString value = "Display"; static const QString value = "Display";
return value; return value;
@ -25,22 +23,14 @@ const QString& DisplayPlugin::MENU_PATH() {
DisplayPluginList getDisplayPlugins() { DisplayPluginList getDisplayPlugins() {
DisplayPlugin* PLUGIN_POOL[] = { DisplayPlugin* PLUGIN_POOL[] = {
new Basic2DWindowOpenGLDisplayPlugin(), new Basic2DWindowOpenGLDisplayPlugin(),
new NullDisplayPlugin(),
#ifdef DEBUG #ifdef DEBUG
new NullDisplayPlugin(),
#endif #endif
// Stereo modes // Stereo modes
// SBS left/right // SBS left/right
new SideBySideStereoDisplayPlugin(), new SideBySideStereoDisplayPlugin(),
// Interleaved left/right // Interleaved left/right
new InterleavedStereoDisplayPlugin(), new InterleavedStereoDisplayPlugin(),
// HMDs
//#ifdef Q_OS_WIN
// // SteamVR SDK
// new OpenVrDisplayPlugin(),
//#endif
nullptr nullptr
}; };

View file

@ -15,6 +15,7 @@
#include <QtOpenGL/QGLWidget> #include <QtOpenGL/QGLWidget>
#include <QtGui/QImage> #include <QtGui/QImage>
#include <QtGui/QOpenGLContext>
#include <gl/GLWidget.h> #include <gl/GLWidget.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
@ -54,6 +55,7 @@ public:
} }
virtual void run() override { virtual void run() override {
OpenGLDisplayPlugin* currentPlugin{ nullptr };
Q_ASSERT(_context); Q_ASSERT(_context);
while (!_shutdown) { while (!_shutdown) {
if (_pendingMainThreadOperation) { if (_pendingMainThreadOperation) {
@ -81,12 +83,13 @@ public:
// Check if we have a new plugin to activate // Check if we have a new plugin to activate
if (_newPlugin != nullptr) { if (_newPlugin != nullptr) {
// Deactivate the old plugin // Deactivate the old plugin
if (_activePlugin != nullptr) { if (currentPlugin != nullptr) {
_activePlugin->uncustomizeContext(); currentPlugin->uncustomizeContext();
currentPlugin->enableDeactivate();
} }
_newPlugin->customizeContext(); _newPlugin->customizeContext();
_activePlugin = _newPlugin; currentPlugin = _newPlugin;
_newPlugin = nullptr; _newPlugin = nullptr;
} }
_context->doneCurrent(); _context->doneCurrent();
@ -94,20 +97,25 @@ public:
} }
// If there's no active plugin, just sleep // If there's no active plugin, just sleep
if (_activePlugin == nullptr) { if (currentPlugin == nullptr) {
QThread::usleep(100); QThread::usleep(100);
continue; continue;
} }
// take the latest texture and present it // take the latest texture and present it
_context->makeCurrent(); _context->makeCurrent();
_activePlugin->present(); if (QOpenGLContext::currentContext() == _context->contextHandle()) {
_context->doneCurrent(); currentPlugin->present();
_context->doneCurrent();
} else {
qWarning() << "Makecurrent failed";
}
} }
_context->makeCurrent(); _context->makeCurrent();
if (_activePlugin) { if (currentPlugin) {
_activePlugin->uncustomizeContext(); currentPlugin->uncustomizeContext();
currentPlugin->enableDeactivate();
} }
_context->doneCurrent(); _context->doneCurrent();
_context->moveToThread(qApp->thread()); _context->moveToThread(qApp->thread());
@ -147,7 +155,6 @@ private:
bool _finishedMainThreadOperation { false }; bool _finishedMainThreadOperation { false };
QThread* _mainThread { nullptr }; QThread* _mainThread { nullptr };
OpenGLDisplayPlugin* _newPlugin { nullptr }; OpenGLDisplayPlugin* _newPlugin { nullptr };
OpenGLDisplayPlugin* _activePlugin { nullptr };
QGLContext* _context { nullptr }; QGLContext* _context { nullptr };
}; };
@ -208,11 +215,16 @@ void OpenGLDisplayPlugin::stop() {
} }
void OpenGLDisplayPlugin::deactivate() { void OpenGLDisplayPlugin::deactivate() {
{
Lock lock(_mutex);
_deactivateWait.wait(lock, [&]{ return _uncustomized; });
}
_timer.stop(); _timer.stop();
DisplayPlugin::deactivate(); DisplayPlugin::deactivate();
} }
void OpenGLDisplayPlugin::customizeContext() { void OpenGLDisplayPlugin::customizeContext() {
_uncustomized = false;
auto presentThread = DependencyManager::get<PresentThread>(); auto presentThread = DependencyManager::get<PresentThread>();
Q_ASSERT(thread() == presentThread->thread()); Q_ASSERT(thread() == presentThread->thread());
@ -233,6 +245,7 @@ void OpenGLDisplayPlugin::uncustomizeContext() {
_plane.reset(); _plane.reset();
} }
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to // SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
// receive keyPress events for the Alt (and Meta) key in a reliable manner. // receive keyPress events for the Alt (and Meta) key in a reliable manner.
@ -380,3 +393,9 @@ QImage OpenGLDisplayPlugin::getScreenshot() const {
}); });
return result; return result;
} }
void OpenGLDisplayPlugin::enableDeactivate() {
Lock lock(_mutex);
_uncustomized = true;
_deactivateWait.notify_one();
}

View file

@ -9,6 +9,8 @@
#include "DisplayPlugin.h" #include "DisplayPlugin.h"
#include <condition_variable>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <GLMHelpers.h> #include <GLMHelpers.h>
@ -18,8 +20,9 @@
class OpenGLDisplayPlugin : public DisplayPlugin { class OpenGLDisplayPlugin : public DisplayPlugin {
protected: protected:
using Mutex = std::recursive_mutex; using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>; using Lock = std::unique_lock<Mutex>;
using Condition = std::condition_variable;
public: public:
OpenGLDisplayPlugin(); OpenGLDisplayPlugin();
virtual void activate() override; virtual void activate() override;
@ -71,7 +74,7 @@ protected:
ProgramPtr _program; ProgramPtr _program;
ShapeWrapperPtr _plane; ShapeWrapperPtr _plane;
Mutex _mutex; mutable Mutex _mutex;
SimpleMovingAverage _usecsPerFrame { 10 }; SimpleMovingAverage _usecsPerFrame { 10 };
QMap<uint32_t, uint32_t> _sceneTextureToFrameIndexMap; QMap<uint32_t, uint32_t> _sceneTextureToFrameIndexMap;
@ -82,6 +85,12 @@ protected:
GLTextureEscrow _sceneTextureEscrow; GLTextureEscrow _sceneTextureEscrow;
bool _vsyncSupported { false }; bool _vsyncSupported { false };
private:
void enableDeactivate();
Condition _deactivateWait;
bool _uncustomized{ false };
}; };

View file

@ -30,7 +30,7 @@ class ZoneEntityItem;
// Generic client side Octree renderer class. // Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public Dependency {
Q_OBJECT Q_OBJECT
public: public:
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,

View file

@ -41,6 +41,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
Q_ASSERT(getType() == EntityTypes::Box); Q_ASSERT(getType() == EntityTypes::Box);
Q_ASSERT(args->_batch); Q_ASSERT(args->_batch);
if (!_procedural) { if (!_procedural) {
_procedural.reset(new Procedural(this->getUserData())); _procedural.reset(new Procedural(this->getUserData()));
_procedural->_vertexSource = simple_vert; _procedural->_vertexSource = simple_vert;
@ -64,4 +65,6 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
} else { } else {
DependencyManager::get<DeferredLightingEffect>()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor); DependencyManager::get<DeferredLightingEffect>()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor);
} }
}; static const auto triCount = DependencyManager::get<GeometryCache>()->getCubeTriangleCount();
args->_details._trianglesRendered += triCount;
}

View file

@ -12,12 +12,14 @@
#include <glm/gtx/quaternion.hpp> #include <glm/gtx/quaternion.hpp>
#include <QJsonDocument> #include <QJsonDocument>
#include <QtCore/QThread>
#include <AbstractViewStateInterface.h> #include <AbstractViewStateInterface.h>
#include <DeferredLightingEffect.h> #include <DeferredLightingEffect.h>
#include <Model.h> #include <Model.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <render/Scene.h> #include <render/Scene.h>
#include <DependencyManager.h>
#include "EntityTreeRenderer.h" #include "EntityTreeRenderer.h"
#include "EntitiesRendererLogging.h" #include "EntitiesRendererLogging.h"
@ -44,6 +46,32 @@ RenderableModelEntityItem::~RenderableModelEntityItem() {
} }
} }
void RenderableModelEntityItem::setModelURL(const QString& url) {
auto& currentURL = getParsedModelURL();
ModelEntityItem::setModelURL(url);
if (currentURL != getParsedModelURL() || !_model) {
EntityTreePointer tree = getTree();
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
}
}
void RenderableModelEntityItem::loader() {
_needsModelReload = true;
EntityTreeRenderer* renderer = DependencyManager::get<EntityTreeRenderer>().data();
assert(renderer);
if (!_model || _needsModelReload) {
PerformanceTimer perfTimer("getModel");
getModel(renderer);
}
if (_model) {
_model->setURL(getParsedModelURL());
_model->setCollisionModelURL(QUrl(getCompoundShapeURL()));
}
}
void RenderableModelEntityItem::setDimensions(const glm::vec3& value) { void RenderableModelEntityItem::setDimensions(const glm::vec3& value) {
_dimensionsInitialized = true; _dimensionsInitialized = true;
ModelEntityItem::setDimensions(value); ModelEntityItem::setDimensions(value);
@ -223,7 +251,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
// check if the URL has changed // check if the URL has changed
auto& currentURL = getParsedModelURL(); auto& currentURL = getParsedModelURL();
if (currentURL != _model->getURL()) { if (currentURL != _model->getURL()) {
qDebug().noquote() << "Updating model URL: " << currentURL.toDisplayString(); qCDebug(entitiesrenderer).noquote() << "Updating model URL: " << currentURL.toDisplayString();
_model->setURL(currentURL); _model->setURL(currentURL);
} }
@ -318,7 +346,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
Model* result = NULL; Model* result = NULL;
if (!renderer) { if (!renderer) {
return result; return result;
} }
@ -340,7 +368,8 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
// if we have a previously allocated model, but its URL doesn't match // if we have a previously allocated model, but its URL doesn't match
// then we need to let our renderer update our model for us. // then we need to let our renderer update our model for us.
if (_model && QUrl(getModelURL()) != _model->getURL()) { if (_model && (QUrl(getModelURL()) != _model->getURL() ||
QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
result = _model = _myRenderer->updateModel(_model, getModelURL(), getCompoundShapeURL()); result = _model = _myRenderer->updateModel(_model, getModelURL(), getCompoundShapeURL());
_needsInitialSimulation = true; _needsInitialSimulation = true;
} else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one } else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one
@ -403,25 +432,29 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
} }
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url); ModelEntityItem::setCompoundShapeURL(url);
if (_model) {
_model->setCollisionModelURL(QUrl(url)); if (getCompoundShapeURL() != currentCompoundShapeURL || !_model) {
EntityTreePointer tree = getTree();
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
} }
} }
bool RenderableModelEntityItem::isReadyToComputeShape() { bool RenderableModelEntityItem::isReadyToComputeShape() {
ShapeType type = getShapeType(); ShapeType type = getShapeType();
if (type == SHAPE_TYPE_COMPOUND) { if (type == SHAPE_TYPE_COMPOUND) {
if (!_model) { if (!_model) {
EntityTreePointer tree = getTree();
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
return false; // hmm... return false; // hmm...
} }
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
return false;
}
assert(!_model->getCollisionURL().isEmpty()); assert(!_model->getCollisionURL().isEmpty());
if (_model->getURL().isEmpty()) { if (_model->getURL().isEmpty()) {
@ -435,6 +468,14 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) && if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) &&
(renderNetworkGeometry && renderNetworkGeometry->isLoaded())) { (renderNetworkGeometry && renderNetworkGeometry->isLoaded())) {
// we have both URLs AND both geometries AND they are both fully loaded. // we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
_needsInitialSimulation = false;
}
return true; return true;
} }

View file

@ -29,7 +29,8 @@ public:
virtual ~RenderableModelEntityItem(); virtual ~RenderableModelEntityItem();
virtual void setDimensions(const glm::vec3& value) override; virtual void setDimensions(const glm::vec3& value) override;
virtual void setModelURL(const QString& url);
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setProperties(const EntityItemProperties& properties) override;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
@ -71,6 +72,8 @@ public:
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual void loader() override;
private: private:
void remapTextures(); void remapTextures();

View file

@ -23,46 +23,73 @@
#include "untextured_particle_frag.h" #include "untextured_particle_frag.h"
#include "textured_particle_vert.h" #include "textured_particle_vert.h"
#include "textured_particle_frag.h" #include "textured_particle_frag.h"
#include "textured_particle_alpha_discard_frag.h"
class ParticlePayload {
class ParticlePayloadData {
public: public:
typedef render::Payload<ParticlePayload> Payload; static const size_t VERTEX_PER_PARTICLE = 4;
typedef Payload::DataPointer Pointer;
typedef RenderableParticleEffectEntityItem::Vertex Vertex;
ParticlePayload(EntityItemPointer entity) : template<typename T>
_entity(entity), struct InterpolationData {
_vertexFormat(std::make_shared<gpu::Stream::Format>()), T start;
_vertexBuffer(std::make_shared<gpu::Buffer>()), T middle;
_indexBuffer(std::make_shared<gpu::Buffer>()) { T finish;
T spread;
_vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, 0); };
_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv)); struct ParticleUniforms {
_vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, offsetof(Vertex, rgba)); InterpolationData<float> radius;
InterpolationData<glm::vec4> color; // rgba
float lifespan;
};
struct ParticlePrimitive {
ParticlePrimitive(glm::vec3 xyzIn, glm::vec2 uvIn) : xyz(xyzIn), uv(uvIn) {}
glm::vec3 xyz; // Position
glm::vec2 uv; // Lifetime + seed
};
using Payload = render::Payload<ParticlePayloadData>;
using Pointer = Payload::DataPointer;
using PipelinePointer = gpu::PipelinePointer;
using FormatPointer = gpu::Stream::FormatPointer;
using BufferPointer = gpu::BufferPointer;
using TexturePointer = gpu::TexturePointer;
using Format = gpu::Stream::Format;
using Buffer = gpu::Buffer;
using BufferView = gpu::BufferView;
using ParticlePrimitives = std::vector<ParticlePrimitive>;
ParticlePayloadData() {
ParticleUniforms uniforms;
_uniformBuffer = std::make_shared<Buffer>(sizeof(ParticleUniforms), (const gpu::Byte*) &uniforms);
_vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ,
offsetof(ParticlePrimitive, xyz), gpu::Stream::PER_INSTANCE);
_vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::VEC2F_UV,
offsetof(ParticlePrimitive, uv), gpu::Stream::PER_INSTANCE);
} }
void setPipeline(gpu::PipelinePointer pipeline) { _pipeline = pipeline; } void setPipeline(PipelinePointer pipeline) { _pipeline = pipeline; }
const gpu::PipelinePointer& getPipeline() const { return _pipeline; } const PipelinePointer& getPipeline() const { return _pipeline; }
const Transform& getModelTransform() const { return _modelTransform; } const Transform& getModelTransform() const { return _modelTransform; }
void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; } void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; }
const AABox& getBound() const { return _bound; } const AABox& getBound() const { return _bound; }
void setBound(AABox& bound) { _bound = bound; } void setBound(const AABox& bound) { _bound = bound; }
gpu::BufferPointer getVertexBuffer() { return _vertexBuffer; } BufferPointer getParticleBuffer() { return _particleBuffer; }
const gpu::BufferPointer& getVertexBuffer() const { return _vertexBuffer; } const BufferPointer& getParticleBuffer() const { return _particleBuffer; }
const ParticleUniforms& getParticleUniforms() const { return _uniformBuffer.get<ParticleUniforms>(); }
ParticleUniforms& editParticleUniforms() { return _uniformBuffer.edit<ParticleUniforms>(); }
gpu::BufferPointer getIndexBuffer() { return _indexBuffer; } void setTexture(TexturePointer texture) { _texture = texture; }
const gpu::BufferPointer& getIndexBuffer() const { return _indexBuffer; } const TexturePointer& getTexture() const { return _texture; }
void setTexture(gpu::TexturePointer texture) { _texture = texture; }
const gpu::TexturePointer& getTexture() const { return _texture; }
bool getVisibleFlag() const { return _visibleFlag; } bool getVisibleFlag() const { return _visibleFlag; }
void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; } void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; }
void render(RenderArgs* args) const { void render(RenderArgs* args) const {
assert(_pipeline); assert(_pipeline);
@ -74,29 +101,28 @@ public:
} }
batch.setModelTransform(_modelTransform); batch.setModelTransform(_modelTransform);
batch.setUniformBuffer(0, _uniformBuffer);
batch.setInputFormat(_vertexFormat); batch.setInputFormat(_vertexFormat);
batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex)); batch.setInputBuffer(0, _particleBuffer, 0, sizeof(ParticlePrimitive));
batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0);
auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t); auto numParticles = _particleBuffer->getSize() / sizeof(ParticlePrimitive);
batch.drawIndexed(gpu::TRIANGLES, numIndices); batch.drawInstanced(numParticles, gpu::TRIANGLE_STRIP, VERTEX_PER_PARTICLE);
} }
protected: protected:
EntityItemPointer _entity;
Transform _modelTransform; Transform _modelTransform;
AABox _bound; AABox _bound;
gpu::PipelinePointer _pipeline; PipelinePointer _pipeline;
gpu::Stream::FormatPointer _vertexFormat; FormatPointer _vertexFormat { std::make_shared<Format>() };
gpu::BufferPointer _vertexBuffer; BufferPointer _particleBuffer { std::make_shared<Buffer>() };
gpu::BufferPointer _indexBuffer; BufferView _uniformBuffer;
gpu::TexturePointer _texture; TexturePointer _texture;
bool _visibleFlag = true; bool _visibleFlag = true;
}; };
namespace render { namespace render {
template <> template <>
const ItemKey payloadGetKey(const ParticlePayload::Pointer& payload) { const ItemKey payloadGetKey(const ParticlePayloadData::Pointer& payload) {
if (payload->getVisibleFlag()) { if (payload->getVisibleFlag()) {
return ItemKey::Builder::transparentShape(); return ItemKey::Builder::transparentShape();
} else { } else {
@ -105,13 +131,15 @@ namespace render {
} }
template <> template <>
const Item::Bound payloadGetBound(const ParticlePayload::Pointer& payload) { const Item::Bound payloadGetBound(const ParticlePayloadData::Pointer& payload) {
return payload->getBound(); return payload->getBound();
} }
template <> template <>
void payloadRender(const ParticlePayload::Pointer& payload, RenderArgs* args) { void payloadRender(const ParticlePayloadData::Pointer& payload, RenderArgs* args) {
payload->render(args); if (payload->getVisibleFlag()) {
payload->render(args);
}
} }
} }
@ -119,7 +147,7 @@ namespace render {
EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID,
const EntityItemProperties& properties) { const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableParticleEffectEntityItem(entityID) }; auto entity = std::make_shared<RenderableParticleEffectEntityItem>(entityID);
entity->setProperties(properties); entity->setProperties(properties);
return entity; return entity;
} }
@ -127,7 +155,7 @@ EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID
RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID) : RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID) :
ParticleEffectEntityItem(entityItemID) { ParticleEffectEntityItem(entityItemID) {
// lazy creation of particle system pipeline // lazy creation of particle system pipeline
if (!_untexturedPipeline && !_texturedPipeline) { if (!_untexturedPipeline || !_texturedPipeline) {
createPipelines(); createPipelines();
} }
} }
@ -135,18 +163,15 @@ RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const Ent
bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self,
render::ScenePointer scene, render::ScenePointer scene,
render::PendingChanges& pendingChanges) { render::PendingChanges& pendingChanges) {
_scene = scene;
auto particlePayload = _renderItemId = _scene->allocateID();
std::shared_ptr<ParticlePayload>(new ParticlePayload(getThisPointer())); auto particlePayloadData = std::make_shared<ParticlePayloadData>();
particlePayload->setPipeline(_untexturedPipeline); particlePayloadData->setPipeline(_untexturedPipeline);
_renderItemId = scene->allocateID(); auto renderPayload = std::make_shared<ParticlePayloadData::Payload>(particlePayloadData);
auto renderData = ParticlePayload::Pointer(particlePayload);
auto renderPayload = render::PayloadPointer(new ParticlePayload::Payload(renderData));
render::Item::Status::Getters statusGetters; render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters); makeEntityItemStatusGetters(getThisPointer(), statusGetters);
renderPayload->addStatusGetters(statusGetters); renderPayload->addStatusGetters(statusGetters);
pendingChanges.resetItem(_renderItemId, renderPayload); pendingChanges.resetItem(_renderItemId, renderPayload);
_scene = scene;
return true; return true;
} }
@ -174,141 +199,71 @@ void RenderableParticleEffectEntityItem::update(const quint64& now) {
updateRenderItem(); updateRenderItem();
} }
uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return ((uint32_t)r | (uint32_t)g << 8 | (uint32_t)b << 16 | (uint32_t)a << 24);
}
class ParticleDetails {
public:
ParticleDetails(glm::vec3 position, float radius, uint32_t rgba) : position(position), radius(radius), rgba(rgba) { }
glm::vec3 position;
float radius;
uint32_t rgba;
};
static glm::vec3 zSortAxis;
static bool zSort(const ParticleDetails& rhs, const ParticleDetails& lhs) {
return glm::dot(rhs.position, ::zSortAxis) > glm::dot(lhs.position, ::zSortAxis);
}
void RenderableParticleEffectEntityItem::updateRenderItem() { void RenderableParticleEffectEntityItem::updateRenderItem() {
if (!_scene) { if (!_scene) {
return; return;
} }
if (!getVisible()) {
// make a copy of each particle's details render::PendingChanges pendingChanges;
std::vector<ParticleDetails> particleDetails; pendingChanges.updateItem<ParticlePayloadData>(_renderItemId, [](ParticlePayloadData& payload) {
particleDetails.reserve(getLivingParticleCount()); payload.setVisibleFlag(false);
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { });
auto xcolor = _particleColors[i];
auto alpha = (uint8_t)(glm::clamp(_particleAlphas[i] * getLocalRenderAlpha(), 0.0f, 1.0f) * 255.0f); _scene->enqueuePendingChanges(pendingChanges);
auto rgba = toRGBA(xcolor.red, xcolor.green, xcolor.blue, alpha); return;
particleDetails.push_back(ParticleDetails(_particlePositions[i], _particleRadiuses[i], rgba));
} }
// sort particles back to front
// NOTE: this is view frustum might be one frame out of date.
auto frustum = AbstractViewStateInterface::instance()->getCurrentViewFrustum(); using ParticleUniforms = ParticlePayloadData::ParticleUniforms;
using ParticlePrimitive = ParticlePayloadData::ParticlePrimitive;
using ParticlePrimitives = ParticlePayloadData::ParticlePrimitives;
// No need to sort if we're doing additive blending // Fill in Uniforms structure
if (_additiveBlending != true) { ParticleUniforms particleUniforms;
::zSortAxis = frustum->getDirection(); particleUniforms.radius.start = getRadiusStart();
qSort(particleDetails.begin(), particleDetails.end(), zSort); particleUniforms.radius.middle = getParticleRadius();
} particleUniforms.radius.finish = getRadiusFinish();
particleUniforms.radius.spread = getRadiusSpread();
particleUniforms.color.start = toGlm(getColorStart(), getAlphaStart());
particleUniforms.color.middle = toGlm(getXColor(), getAlpha());
// allocate vertices particleUniforms.color.finish = toGlm(getColorFinish(), getAlphaFinish());
_vertices.clear(); particleUniforms.color.spread = toGlm(getColorSpread(), getAlphaSpread());
particleUniforms.lifespan = getLifespan();
// build vertices from particle positions and radiuses
glm::vec3 dir = frustum->getDirection(); // Build particle primitives
for (auto&& particle : particleDetails) { auto particlePrimitives = std::make_shared<ParticlePrimitives>();
glm::vec3 right = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), dir)); particlePrimitives->reserve(_particles.size()); // Reserve space
glm::vec3 up = glm::normalize(glm::cross(right, dir)); for (auto& particle : _particles) {
particlePrimitives->emplace_back(particle.position, glm::vec2(particle.lifetime, particle.seed));
glm::vec3 upOffset = up * particle.radius;
glm::vec3 rightOffset = right * particle.radius;
// generate corners of quad aligned to face the camera.
_vertices.emplace_back(particle.position + rightOffset + upOffset, glm::vec2(1.0f, 1.0f), particle.rgba);
_vertices.emplace_back(particle.position - rightOffset + upOffset, glm::vec2(0.0f, 1.0f), particle.rgba);
_vertices.emplace_back(particle.position - rightOffset - upOffset, glm::vec2(0.0f, 0.0f), particle.rgba);
_vertices.emplace_back(particle.position + rightOffset - upOffset, glm::vec2(1.0f, 0.0f), particle.rgba);
} }
auto bounds = getAABox();
auto position = getPosition();
auto rotation = getRotation();
Transform transform;
transform.setTranslation(position);
transform.setRotation(rotation);
render::PendingChanges pendingChanges; render::PendingChanges pendingChanges;
pendingChanges.updateItem<ParticlePayload>(_renderItemId, [this](ParticlePayload& payload) { pendingChanges.updateItem<ParticlePayloadData>(_renderItemId, [=](ParticlePayloadData& payload) {
// update vertex buffer payload.setVisibleFlag(true);
auto vertexBuffer = payload.getVertexBuffer();
size_t numBytes = sizeof(Vertex) * _vertices.size(); // Update particle uniforms
memcpy(&payload.editParticleUniforms(), &particleUniforms, sizeof(ParticleUniforms));
// Update particle buffer
auto particleBuffer = payload.getParticleBuffer();
size_t numBytes = sizeof(ParticlePrimitive) * particlePrimitives->size();
particleBuffer->resize(numBytes);
if (numBytes == 0) { if (numBytes == 0) {
vertexBuffer->resize(0);
auto indexBuffer = payload.getIndexBuffer();
indexBuffer->resize(0);
return; return;
} }
memcpy(particleBuffer->editData(), particlePrimitives->data(), numBytes);
vertexBuffer->resize(numBytes); // Update transform and bounds
gpu::Byte* data = vertexBuffer->editData(); payload.setModelTransform(transform);
memcpy(data, &(_vertices[0]), numBytes); payload.setBound(bounds);
// FIXME, don't update index buffer if num particles has not changed. if (_texture && _texture->isLoaded()) {
// update index buffer
auto indexBuffer = payload.getIndexBuffer();
const size_t NUM_VERTS_PER_PARTICLE = 4;
const size_t NUM_INDICES_PER_PARTICLE = 6;
auto numQuads = (_vertices.size() / NUM_VERTS_PER_PARTICLE);
numBytes = sizeof(uint16_t) * numQuads * NUM_INDICES_PER_PARTICLE;
indexBuffer->resize(numBytes);
data = indexBuffer->editData();
auto indexPtr = reinterpret_cast<uint16_t*>(data);
for (size_t i = 0; i < numQuads; ++i) {
indexPtr[i * NUM_INDICES_PER_PARTICLE + 0] = i * NUM_VERTS_PER_PARTICLE + 0;
indexPtr[i * NUM_INDICES_PER_PARTICLE + 1] = i * NUM_VERTS_PER_PARTICLE + 1;
indexPtr[i * NUM_INDICES_PER_PARTICLE + 2] = i * NUM_VERTS_PER_PARTICLE + 3;
indexPtr[i * NUM_INDICES_PER_PARTICLE + 3] = i * NUM_VERTS_PER_PARTICLE + 1;
indexPtr[i * NUM_INDICES_PER_PARTICLE + 4] = i * NUM_VERTS_PER_PARTICLE + 2;
indexPtr[i * NUM_INDICES_PER_PARTICLE + 5] = i * NUM_VERTS_PER_PARTICLE + 3;
}
// update transform
glm::quat rot = getRotation();
glm::vec3 pos = getPosition();
Transform t;
t.setRotation(rot);
payload.setModelTransform(t);
// transform _particleMinBound and _particleMaxBound corners into world coords
glm::vec3 d = _particleMaxBound - _particleMinBound;
const size_t NUM_BOX_CORNERS = 8;
glm::vec3 corners[NUM_BOX_CORNERS] = {
pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, 0.0f)),
pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, 0.0f)),
pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, 0.0f)),
pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, 0.0f)),
pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, d.z)),
pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, d.z)),
pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, d.z)),
pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, d.z))
};
glm::vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
glm::vec3 max = -min;
for (size_t i = 0; i < NUM_BOX_CORNERS; i++) {
min.x = std::min(min.x, corners[i].x);
min.y = std::min(min.y, corners[i].y);
min.z = std::min(min.z, corners[i].z);
max.x = std::max(max.x, corners[i].x);
max.y = std::max(max.y, corners[i].y);
max.z = std::max(max.z, corners[i].z);
}
AABox bound(min, max - min);
payload.setBound(bound);
bool textured = _texture && _texture->isLoaded();
if (textured) {
payload.setTexture(_texture->getGPUTexture()); payload.setTexture(_texture->getGPUTexture());
payload.setPipeline(_texturedPipeline); payload.setPipeline(_texturedPipeline);
} else { } else {
@ -321,46 +276,29 @@ void RenderableParticleEffectEntityItem::updateRenderItem() {
} }
void RenderableParticleEffectEntityItem::createPipelines() { void RenderableParticleEffectEntityItem::createPipelines() {
bool writeToDepthBuffer = false;
gpu::State::BlendArg destinationColorBlendArg;
if (_additiveBlending) {
destinationColorBlendArg = gpu::State::ONE;
}
else {
destinationColorBlendArg = gpu::State::INV_SRC_ALPHA;
writeToDepthBuffer = true;
}
if (!_untexturedPipeline) { if (!_untexturedPipeline) {
auto state = std::make_shared<gpu::State>(); auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK); state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, writeToDepthBuffer, gpu::LESS_EQUAL); state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
destinationColorBlendArg, gpu::State::FACTOR_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
gpu::State::BLEND_OP_ADD, gpu::State::ONE);
auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert)); auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert));
auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag)); auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag));
auto program = gpu::Shader::createProgram(vertShader, fragShader); auto program = gpu::Shader::createProgram(vertShader, fragShader);
_untexturedPipeline = gpu::Pipeline::create(program, state); _untexturedPipeline = gpu::Pipeline::create(program, state);
} }
if (!_texturedPipeline) { if (!_texturedPipeline) {
auto state = std::make_shared<gpu::State>(); auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK); state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
bool writeToDepthBuffer = !_additiveBlending;
state->setDepthTest(true, writeToDepthBuffer, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD,
destinationColorBlendArg, gpu::State::FACTOR_ALPHA,
gpu::State::BLEND_OP_ADD, gpu::State::ONE);
auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert));
gpu::ShaderPointer fragShader; auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag));
if (_additiveBlending) {
fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag));
}
else {
//If we are sorting and have no additive blending, we want to discard pixels with low alpha to avoid inter-particle entity artifacts
fragShader = gpu::Shader::createPixel(std::string(textured_particle_alpha_discard_frag));
}
auto program = gpu::Shader::createProgram(vertShader, fragShader); auto program = gpu::Shader::createProgram(vertShader, fragShader);
_texturedPipeline = gpu::Pipeline::create(program, state); _texturedPipeline = gpu::Pipeline::create(program, state);
} }

View file

@ -16,7 +16,7 @@
#include "RenderableEntityItem.h" #include "RenderableEntityItem.h"
class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem {
friend class ParticlePayload; friend class ParticlePayloadData;
public: public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableParticleEffectEntityItem(const EntityItemID& entityItemID); RenderableParticleEffectEntityItem(const EntityItemID& entityItemID);
@ -29,23 +29,14 @@ public:
virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) override; virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) override;
protected: protected:
render::ItemID _renderItemId;
struct Vertex {
Vertex(glm::vec3 xyzIn, glm::vec2 uvIn, uint32_t rgbaIn) : xyz(xyzIn), uv(uvIn), rgba(rgbaIn) {}
glm::vec3 xyz;
glm::vec2 uv;
uint32_t rgba;
};
void createPipelines(); void createPipelines();
std::vector<Vertex> _vertices; render::ScenePointer _scene;
render::ItemID _renderItemId;
NetworkTexturePointer _texture;
gpu::PipelinePointer _untexturedPipeline; gpu::PipelinePointer _untexturedPipeline;
gpu::PipelinePointer _texturedPipeline; gpu::PipelinePointer _texturedPipeline;
render::ScenePointer _scene;
NetworkTexturePointer _texture;
}; };

View file

@ -71,4 +71,6 @@ void RenderableSphereEntityItem::render(RenderArgs* args) {
batch.setModelTransform(Transform()); batch.setModelTransform(Transform());
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, modelTransform, sphereColor); DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, modelTransform, sphereColor);
} }
}; static const auto triCount = DependencyManager::get<GeometryCache>()->getSphereTriangleCount();
args->_details._trianglesRendered += triCount;
}

View file

@ -11,12 +11,11 @@
uniform sampler2D colorMap; uniform sampler2D colorMap;
in vec4 _color; in vec4 varColor;
in vec2 _texCoord0; in vec2 varTexcoord;
out vec4 outFragColor; out vec4 outFragColor;
void main(void) { void main(void) {
vec4 color = texture(colorMap, _texCoord0); outFragColor = texture(colorMap, varTexcoord.xy) * varColor;
outFragColor = color * _color;
} }

View file

@ -10,21 +10,84 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
<@include gpu/Inputs.slh@>
<@include gpu/Transform.slh@> <@include gpu/Transform.slh@>
<$declareStandardTransform()$> <$declareStandardTransform()$>
out vec4 _color; struct Radii {
out vec2 _texCoord0; float start;
float middle;
float finish;
float spread;
};
struct Colors {
vec4 start;
vec4 middle;
vec4 finish;
vec4 spread;
};
struct ParticleUniforms {
Radii radius;
Colors color;
float lifespan;
};
uniform particleBuffer {
ParticleUniforms particle;
};
in vec3 inPosition;
in vec2 inColor; // This is actual Lifetime + Seed
out vec4 varColor;
out vec2 varTexcoord;
const int NUM_VERTICES_PER_PARTICLE = 4;
const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE](
vec4(-1.0, -1.0, 0.0, 0.0),
vec4(1.0, -1.0, 0.0, 0.0),
vec4(-1.0, 1.0, 0.0, 0.0),
vec4(1.0, 1.0, 0.0, 0.0)
);
float bezierInterpolate(float y1, float y2, float y3, float u) {
// https://en.wikipedia.org/wiki/Bezier_curve
return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3;
}
vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) {
return vec4(bezierInterpolate(y1.x, y2.x, y3.x, u),
bezierInterpolate(y1.y, y2.y, y3.y, u),
bezierInterpolate(y1.z, y2.z, y3.z, u),
bezierInterpolate(y1.w, y2.w, y3.w, u));
}
void main(void) { void main(void) {
// pass along the color & uvs to fragment shader
_color = inColor;
_texCoord0 = inTexCoord0.xy;
TransformCamera cam = getTransformCamera(); TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject(); TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
// Which icon are we dealing with ?
int particleID = gl_VertexID / NUM_VERTICES_PER_PARTICLE;
// Which quad vertex pos?
int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE;
// Particle properties
float age = inColor.x / particle.lifespan;
float seed = inColor.y;
// Pass the texcoord and the z texcoord is representing the texture icon
varTexcoord = vec2((UNIT_QUAD[twoTriID].xy + 1.0) * 0.5);
varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age);
// anchor point in eye space
float radius = bezierInterpolate(particle.radius.start, particle.radius.middle, particle.radius.finish , age);
vec4 quadPos = radius * UNIT_QUAD[twoTriID];
vec4 anchorPoint;
<$transformModelToEyePos(cam, obj, inPosition, anchorPoint)$>
vec4 eyePos = anchorPoint + quadPos;
<$transformEyeToClipPos(cam, eyePos, gl_Position)$>
} }

View file

@ -1,25 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// fragment shader
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
uniform sampler2D colorMap;
in vec4 _color;
in vec2 _texCoord0;
out vec4 outFragColor;
void main(void) {
vec4 color = texture(colorMap, _texCoord0);
if (color.a < 0.1) {
discard;
}
outFragColor = color * _color;
}

View file

@ -29,7 +29,6 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame, _animationLoop->getFirstFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame, _animationLoop->getFirstFrame);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame, _animationLoop->getLastFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame, _animationLoop->getLastFrame);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold, _animationLoop->getHold); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold, _animationLoop->getHold);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_START_AUTOMATICALLY, Animation, animation, StartAutomatically, startAutomatically, _animationLoop->getStartAutomatically);
} else { } else {
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, Animation, animation, FPS, fps);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame);
@ -38,7 +37,6 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_START_AUTOMATICALLY, Animation, animation, StartAutomatically, startAutomatically);
} }
} }
@ -58,7 +56,6 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, _animationLoop->setFirstFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, _animationLoop->setFirstFrame);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, _animationLoop->setLastFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, _animationLoop->setLastFrame);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, _animationLoop->setHold); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, _animationLoop->setHold);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, startAutomatically, bool, _animationLoop->setStartAutomatically);
// legacy property support // legacy property support
COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, _animationLoop->setFPS, _animationLoop->getFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, _animationLoop->setFPS, _animationLoop->getFPS);
@ -73,7 +70,6 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, setFirstFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, setFirstFrame);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, setLastFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, setLastFrame);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, setHold); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, setHold);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, startAutomatically, bool, setStartAutomatically);
// legacy property support // legacy property support
COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, setFPS, getFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, setFPS, getFPS);
@ -95,7 +91,6 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
float lastFrame = _animationLoop ? _animationLoop->getLastFrame() : getLastFrame(); float lastFrame = _animationLoop ? _animationLoop->getLastFrame() : getLastFrame();
bool loop = _animationLoop ? _animationLoop->getLoop() : getLoop(); bool loop = _animationLoop ? _animationLoop->getLoop() : getLoop();
bool hold = _animationLoop ? _animationLoop->getHold() : getHold(); bool hold = _animationLoop ? _animationLoop->getHold() : getHold();
bool startAutomatically = _animationLoop ? _animationLoop->getStartAutomatically() : getStartAutomatically();
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
QJsonObject settingsAsJsonObject = settingsAsJson.object(); QJsonObject settingsAsJsonObject = settingsAsJson.object();
@ -130,10 +125,6 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
running = settingsMap["hold"].toBool(); running = settingsMap["hold"].toBool();
} }
if (settingsMap.contains("startAutomatically")) {
running = settingsMap["startAutomatically"].toBool();
}
if (_animationLoop) { if (_animationLoop) {
_animationLoop->setFPS(fps); _animationLoop->setFPS(fps);
_animationLoop->setCurrentFrame(currentFrame); _animationLoop->setCurrentFrame(currentFrame);
@ -142,7 +133,6 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
_animationLoop->setLastFrame(lastFrame); _animationLoop->setLastFrame(lastFrame);
_animationLoop->setLoop(loop); _animationLoop->setLoop(loop);
_animationLoop->setHold(hold); _animationLoop->setHold(hold);
_animationLoop->setStartAutomatically(startAutomatically);
} else { } else {
setFPS(fps); setFPS(fps);
setCurrentFrame(currentFrame); setCurrentFrame(currentFrame);
@ -151,7 +141,6 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
setLastFrame(lastFrame); setLastFrame(lastFrame);
setLoop(loop); setLoop(loop);
setHold(hold); setHold(hold);
setStartAutomatically(startAutomatically);
} }
} }
@ -194,7 +183,6 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, _animationLoop->getStartAutomatically());
} else { } else {
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame());
@ -203,7 +191,6 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, getStartAutomatically());
} }
return true; return true;
@ -226,7 +213,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF
READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold);
READ_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, bool, _animationLoop->setStartAutomatically);
} else { } else {
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame);
@ -235,7 +221,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF
READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold);
READ_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, bool, setStartAutomatically);
} }
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_URL, URL); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_URL, URL);
@ -246,7 +231,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FIRST_FRAME, FirstFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FIRST_FRAME, FirstFrame);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_LAST_FRAME, LastFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_LAST_FRAME, LastFrame);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_HOLD, Hold); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_HOLD, Hold);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_START_AUTOMATICALLY, StartAutomatically);
processedBytes += bytesRead; processedBytes += bytesRead;
@ -273,7 +257,6 @@ EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FIRST_FRAME, firstFrame); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FIRST_FRAME, firstFrame);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_LAST_FRAME, lastFrame); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_LAST_FRAME, lastFrame);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_HOLD, hold); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_HOLD, hold);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_START_AUTOMATICALLY, startAutomatically);
return changedProperties; return changedProperties;
} }
@ -288,7 +271,6 @@ void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) con
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, _animationLoop->getFirstFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, _animationLoop->getFirstFrame);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, _animationLoop->getLastFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, _animationLoop->getLastFrame);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, _animationLoop->getHold); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, _animationLoop->getHold);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, StartAutomatically, _animationLoop->getStartAutomatically);
} else { } else {
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FPS, getFPS); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FPS, getFPS);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, CurrentFrame, getCurrentFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, CurrentFrame, getCurrentFrame);
@ -297,7 +279,6 @@ void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) con
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, getFirstFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, getFirstFrame);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, getLastFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, getLastFrame);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, getHold); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, getHold);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, StartAutomatically, getStartAutomatically);
} }
} }
@ -313,7 +294,6 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, _animationLoop->setFirstFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, _animationLoop->setFirstFrame);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, _animationLoop->setLastFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, _animationLoop->setLastFrame);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, _animationLoop->setHold); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, _animationLoop->setHold);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, StartAutomatically, startAutomatically, _animationLoop->setStartAutomatically);
} else { } else {
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FPS, fps, setFPS); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FPS, fps, setFPS);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, CurrentFrame, currentFrame, setCurrentFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, CurrentFrame, currentFrame, setCurrentFrame);
@ -322,7 +302,6 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, setFirstFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, setFirstFrame);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, setLastFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, setLastFrame);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, setHold); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, setHold);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, StartAutomatically, startAutomatically, setStartAutomatically);
} }
return somethingChanged; return somethingChanged;
@ -339,7 +318,6 @@ EntityPropertyFlags AnimationPropertyGroup::getEntityProperties(EncodeBitstreamP
requestedProperties += PROP_ANIMATION_FIRST_FRAME; requestedProperties += PROP_ANIMATION_FIRST_FRAME;
requestedProperties += PROP_ANIMATION_LAST_FRAME; requestedProperties += PROP_ANIMATION_LAST_FRAME;
requestedProperties += PROP_ANIMATION_HOLD; requestedProperties += PROP_ANIMATION_HOLD;
requestedProperties += PROP_ANIMATION_START_AUTOMATICALLY;
return requestedProperties; return requestedProperties;
} }
@ -363,7 +341,6 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, _animationLoop->getStartAutomatically());
} else { } else {
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame());
@ -372,7 +349,6 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, getStartAutomatically());
} }
} }
@ -395,7 +371,6 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char
READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold);
READ_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, bool, _animationLoop->setStartAutomatically);
} else { } else {
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame);
@ -404,7 +379,6 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char
READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold);
READ_ENTITY_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, bool, setStartAutomatically);
} }
return bytesRead; return bytesRead;

View file

@ -80,7 +80,6 @@ public:
DEFINE_PROPERTY(PROP_ANIMATION_FIRST_FRAME, FirstFrame, firstFrame, float, 0.0f); // was animationSettings.firstFrame DEFINE_PROPERTY(PROP_ANIMATION_FIRST_FRAME, FirstFrame, firstFrame, float, 0.0f); // was animationSettings.firstFrame
DEFINE_PROPERTY(PROP_ANIMATION_LAST_FRAME, LastFrame, lastFrame, float, AnimationLoop::MAXIMUM_POSSIBLE_FRAME); // was animationSettings.lastFrame DEFINE_PROPERTY(PROP_ANIMATION_LAST_FRAME, LastFrame, lastFrame, float, AnimationLoop::MAXIMUM_POSSIBLE_FRAME); // was animationSettings.lastFrame
DEFINE_PROPERTY(PROP_ANIMATION_HOLD, Hold, hold, bool, false); // was animationSettings.hold DEFINE_PROPERTY(PROP_ANIMATION_HOLD, Hold, hold, bool, false); // was animationSettings.hold
DEFINE_PROPERTY(PROP_ANIMATION_START_AUTOMATICALLY, StartAutomatically, startAutomatically, bool, false); // was animationSettings.startAutomatically
protected: protected:
void setFromOldAnimationSettings(const QString& value); void setFromOldAnimationSettings(const QString& value);

View file

@ -36,7 +36,7 @@ int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
EntityItem::EntityItem(const EntityItemID& entityItemID) : EntityItem::EntityItem(const EntityItemID& entityItemID) :
SpatiallyNestable(NestableTypes::Entity, entityItemID), SpatiallyNestable(NestableType::Entity, entityItemID),
_type(EntityTypes::Unknown), _type(EntityTypes::Unknown),
_lastSimulated(0), _lastSimulated(0),
_lastUpdated(0), _lastUpdated(0),
@ -628,71 +628,50 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) { // pack SimulationOwner and terse update properties near each other
// pack SimulationOwner and terse update properties near each other
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data // NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
// even when we would otherwise ignore the rest of the packet. // even when we would otherwise ignore the rest of the packet.
if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) { if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) {
QByteArray simOwnerData; QByteArray simOwnerData;
int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData); int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData);
SimulationOwner newSimOwner; SimulationOwner newSimOwner;
newSimOwner.fromByteArray(simOwnerData); newSimOwner.fromByteArray(simOwnerData);
dataAt += bytes; dataAt += bytes;
bytesRead += bytes; bytesRead += bytes;
if (wantTerseEditLogging() && _simulationOwner != newSimOwner) { if (wantTerseEditLogging() && _simulationOwner != newSimOwner) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << newSimOwner; qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << newSimOwner;
}
if (_simulationOwner.set(newSimOwner)) {
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
}
} }
{ // When we own the simulation we don't accept updates to the entity's transform/velocities if (_simulationOwner.set(newSimOwner)) {
// but since we're using macros below we have to temporarily modify overwriteLocalData. _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
bool oldOverwrite = overwriteLocalData;
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
overwriteLocalData = oldOverwrite;
} }
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions);
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
} else {
// legacy order of packing here
// TODO: purge this logic in a few months from now (2015.07)
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
} }
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
// but since we're using macros below we have to temporarily modify overwriteLocalData.
bool oldOverwrite = overwriteLocalData;
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
overwriteLocalData = oldOverwrite;
}
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions);
READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity);
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible);
@ -701,17 +680,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
if (args.bitstreamVersion < VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) {
// this code for when there is only simulatorID and no simulation priority
// we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true
// before we try to READ_ENTITY_PROPERTY it
bool temp = overwriteLocalData;
overwriteLocalData = true;
READ_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, QUuid, updateSimulatorID);
overwriteLocalData = temp;
}
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
} }
@ -1104,7 +1072,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false; bool somethingChanged = false;
// these affect TerseUpdate properties // these affect TerseUpdate properties
SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, updateSimulationOwner);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity);
@ -1324,6 +1292,13 @@ void EntityItem::updatePosition(const glm::vec3& value) {
} }
if (getLocalPosition() != value) { if (getLocalPosition() != value) {
setLocalPosition(value); setLocalPosition(value);
_dirtyFlags |= Simulation::DIRTY_POSITION;
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->_dirtyFlags |= Simulation::DIRTY_POSITION;
}
});
} }
} }
@ -1342,7 +1317,7 @@ void EntityItem::updateRotation(const glm::quat& rotation) {
setLocalOrientation(rotation); setLocalOrientation(rotation);
_dirtyFlags |= Simulation::DIRTY_ROTATION; _dirtyFlags |= Simulation::DIRTY_ROTATION;
forEachDescendant([&](SpatiallyNestablePointer object) { forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableTypes::Entity) { if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object); EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->_dirtyFlags |= Simulation::DIRTY_ROTATION; entity->_dirtyFlags |= Simulation::DIRTY_ROTATION;
entity->_dirtyFlags |= Simulation::DIRTY_POSITION; entity->_dirtyFlags |= Simulation::DIRTY_POSITION;
@ -1496,12 +1471,12 @@ void EntityItem::setSimulationOwner(const SimulationOwner& owner) {
_simulationOwner.set(owner); _simulationOwner.set(owner);
} }
void EntityItem::updateSimulatorID(const QUuid& value) { void EntityItem::updateSimulationOwner(const SimulationOwner& owner) {
if (wantTerseEditLogging() && _simulationOwner.getID() != value) { if (wantTerseEditLogging() && _simulationOwner != owner) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << value; qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << owner;
} }
if (_simulationOwner.setID(value)) { if (_simulationOwner.set(owner)) {
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
} }
} }

View file

@ -288,7 +288,7 @@ public:
quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); } quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); }
QUuid getSimulatorID() const { return _simulationOwner.getID(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); }
void updateSimulatorID(const QUuid& value); void updateSimulationOwner(const SimulationOwner& owner);
void clearSimulationOwnership(); void clearSimulationOwnership();
const QString& getMarketplaceID() const { return _marketplaceID; } const QString& getMarketplaceID() const { return _marketplaceID; }
@ -382,6 +382,8 @@ public:
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); } virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); }
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); } virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); }
virtual void loader() {} // called indirectly when urls for geometry are updated
protected: protected:
const QByteArray getActionDataInternal() const; const QByteArray getActionDataInternal() const;

View file

@ -747,7 +747,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_START_AUTOMATICALLY, Animation, animation, StartAutomatically, startAutomatically);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center); ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius); ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius);

View file

@ -219,20 +219,27 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
// if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse
QQueue<SpatiallyNestablePointer> toProcess; QQueue<SpatiallyNestablePointer> toProcess;
foreach (SpatiallyNestablePointer child, entity->getChildren()) { foreach (SpatiallyNestablePointer child, entity->getChildren()) {
if (child && child->getNestableType() == NestableTypes::Entity) { if (child && child->getNestableType() == NestableType::Entity) {
toProcess.enqueue(child); toProcess.enqueue(child);
} }
} }
while (!toProcess.empty()) { while (!toProcess.empty()) {
EntityItemPointer childEntity = std::static_pointer_cast<EntityItem>(toProcess.dequeue()); EntityItemPointer childEntity = std::static_pointer_cast<EntityItem>(toProcess.dequeue());
if (!childEntity) {
continue;
}
BoundingBoxRelatedProperties newChildBBRelProperties(childEntity); BoundingBoxRelatedProperties newChildBBRelProperties(childEntity);
EntityTreeElementPointer containingElement = childEntity->getElement();
if (!containingElement) {
continue;
}
UpdateEntityOperator theChildOperator(getThisPointer(), UpdateEntityOperator theChildOperator(getThisPointer(),
childEntity->getElement(), containingElement,
childEntity, newChildBBRelProperties); childEntity, newChildBBRelProperties);
recurseTreeWithOperator(&theChildOperator); recurseTreeWithOperator(&theChildOperator);
foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) {
if (childChild && childChild->getNestableType() == NestableTypes::Entity) { if (childChild && childChild->getNestableType() == NestableType::Entity) {
toProcess.enqueue(childChild); toProcess.enqueue(childChild);
} }
} }
@ -1266,3 +1273,12 @@ void EntityTree::trackIncomingEntityLastEdited(quint64 lastEditedTime, int bytes
} }
} }
} }
void EntityTree::callLoader(EntityItemID entityID) {
// this is used to bounce from the networking thread to the main thread
EntityItemPointer entity = findEntityByEntityItemID(entityID);
if (entity) {
entity->loader();
}
}

View file

@ -235,6 +235,9 @@ public:
return _deletedEntityItemIDs.contains(id); return _deletedEntityItemIDs.contains(id);
} }
public slots:
void callLoader(EntityItemID entityID);
signals: signals:
void deletingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID);
void addingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID);

View file

@ -384,11 +384,6 @@ void ModelEntityItem::setAnimationSettings(const QString& value) {
setAnimationHold(hold); setAnimationHold(hold);
} }
if (settingsMap.contains("startAutomatically")) {
bool startAutomatically = settingsMap["startAutomatically"].toBool();
setAnimationStartAutomatically(startAutomatically);
}
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE; _dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
} }

View file

@ -74,11 +74,10 @@ public:
_color[GREEN_INDEX] = value.green; _color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue; _color[BLUE_INDEX] = value.blue;
} }
// model related properties
void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
virtual void setCompoundShapeURL(const QString& url);
// model related properties
virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
virtual void setCompoundShapeURL(const QString& url);
// Animation related items... // Animation related items...
const AnimationPropertyGroup& getAnimationProperties() const { return _animationProperties; } const AnimationPropertyGroup& getAnimationProperties() const { return _animationProperties; }
@ -97,9 +96,6 @@ public:
void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } void setAnimationHold(bool hold) { _animationLoop.setHold(hold); }
bool getAnimationHold() const { return _animationLoop.getHold(); } bool getAnimationHold() const { return _animationLoop.getHold(); }
void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); }
bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); }
void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); }
float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); }

View file

@ -40,9 +40,6 @@
#include "EntityScriptingInterface.h" #include "EntityScriptingInterface.h"
#include "ParticleEffectEntityItem.h" #include "ParticleEffectEntityItem.h"
const glm::vec3 X_AXIS = glm::vec3(1.0f, 0.0f, 0.0f);
const glm::vec3 Z_AXIS = glm::vec3(0.0f, 0.0f, 1.0f);
const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work
const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 }; const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 };
@ -66,8 +63,8 @@ const float ParticleEffectEntityItem::DEFAULT_EMIT_SPEED = 5.0f;
const float ParticleEffectEntityItem::MINIMUM_EMIT_SPEED = 0.0f; const float ParticleEffectEntityItem::MINIMUM_EMIT_SPEED = 0.0f;
const float ParticleEffectEntityItem::MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3 const float ParticleEffectEntityItem::MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3
const float ParticleEffectEntityItem::DEFAULT_SPEED_SPREAD = 1.0f; const float ParticleEffectEntityItem::DEFAULT_SPEED_SPREAD = 1.0f;
const glm::quat ParticleEffectEntityItem::DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, X_AXIS); // Vertical const glm::quat ParticleEffectEntityItem::DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical
const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIMENSIONS = glm::vec3(0.0f, 0.0f, 0.0f); // Emit from point const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIMENSIONS = Vectors::ZERO; // Emit from point
const float ParticleEffectEntityItem::MINIMUM_EMIT_DIMENSION = 0.0f; const float ParticleEffectEntityItem::MINIMUM_EMIT_DIMENSION = 0.0f;
const float ParticleEffectEntityItem::MAXIMUM_EMIT_DIMENSION = (float)TREE_SCALE; const float ParticleEffectEntityItem::MAXIMUM_EMIT_DIMENSION = (float)TREE_SCALE;
const float ParticleEffectEntityItem::DEFAULT_EMIT_RADIUS_START = 1.0f; // Emit from surface (when emitDimensions > 0) const float ParticleEffectEntityItem::DEFAULT_EMIT_RADIUS_START = 1.0f; // Emit from surface (when emitDimensions > 0)
@ -106,36 +103,12 @@ EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID
// our non-pure virtual subclass for now... // our non-pure virtual subclass for now...
ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID) : ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID) :
EntityItem(entityItemID), EntityItem(entityItemID),
_lastSimulated(usecTimestampNow()), _lastSimulated(usecTimestampNow())
_particleLifetimes(DEFAULT_MAX_PARTICLES, 0.0f),
_particlePositions(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)),
_particleVelocities(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)),
_particleAccelerations(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)),
_particleRadiuses(DEFAULT_MAX_PARTICLES, DEFAULT_PARTICLE_RADIUS),
_radiusStarts(DEFAULT_MAX_PARTICLES, DEFAULT_PARTICLE_RADIUS),
_radiusMiddles(DEFAULT_MAX_PARTICLES, DEFAULT_PARTICLE_RADIUS),
_radiusFinishes(DEFAULT_MAX_PARTICLES, DEFAULT_PARTICLE_RADIUS),
_particleColors(DEFAULT_MAX_PARTICLES, DEFAULT_COLOR),
_colorStarts(DEFAULT_MAX_PARTICLES, DEFAULT_COLOR),
_colorMiddles(DEFAULT_MAX_PARTICLES, DEFAULT_COLOR),
_colorFinishes(DEFAULT_MAX_PARTICLES, DEFAULT_COLOR),
_particleAlphas(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA),
_alphaStarts(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA),
_alphaMiddles(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA),
_alphaFinishes(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA),
_particleMaxBound(glm::vec3(1.0f, 1.0f, 1.0f)),
_particleMinBound(glm::vec3(-1.0f, -1.0f, -1.0f)) ,
_additiveBlending(DEFAULT_ADDITIVE_BLENDING)
{ {
_type = EntityTypes::ParticleEffect; _type = EntityTypes::ParticleEffect;
setColor(DEFAULT_COLOR); setColor(DEFAULT_COLOR);
} }
ParticleEffectEntityItem::~ParticleEffectEntityItem() {
}
void ParticleEffectEntityItem::setAlpha(float alpha) { void ParticleEffectEntityItem::setAlpha(float alpha) {
if (MINIMUM_ALPHA <= alpha && alpha <= MAXIMUM_ALPHA) { if (MINIMUM_ALPHA <= alpha && alpha <= MAXIMUM_ALPHA) {
_alpha = alpha; _alpha = alpha;
@ -310,8 +283,8 @@ void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) {
void ParticleEffectEntityItem::computeAndUpdateDimensions() { void ParticleEffectEntityItem::computeAndUpdateDimensions() {
const float time = _lifespan * 1.1f; // add 10% extra time to account for incremental timer accumulation error const float time = _lifespan * 1.1f; // add 10% extra time to account for incremental timer accumulation error
glm::vec3 velocity = _emitSpeed * (_emitOrientation * Z_AXIS); glm::vec3 velocity = _emitSpeed * (_emitOrientation * Vectors::UNIT_Z);
glm::vec3 velocitySpread = _speedSpread * (_emitOrientation * Z_AXIS); glm::vec3 velocitySpread = _speedSpread * (_emitOrientation * Vectors::UNIT_Z);
glm::vec3 maxVelocity = glm::abs(velocity) + velocitySpread; glm::vec3 maxVelocity = glm::abs(velocity) + velocitySpread;
glm::vec3 maxAccleration = glm::abs(_acceleration) + _accelerationSpread; glm::vec3 maxAccleration = glm::abs(_acceleration) + _accelerationSpread;
@ -578,7 +551,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
bool ParticleEffectEntityItem::isEmittingParticles() const { bool ParticleEffectEntityItem::isEmittingParticles() const {
// keep emitting if there are particles still alive. // keep emitting if there are particles still alive.
return (getIsEmitting() || getLivingParticleCount() > 0); return (getIsEmitting() || !_particles.empty());
} }
bool ParticleEffectEntityItem::needsToCallUpdate() const { bool ParticleEffectEntityItem::needsToCallUpdate() const {
@ -613,62 +586,25 @@ void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
} }
} }
void ParticleEffectEntityItem::updateRadius(quint32 index, float age) { void ParticleEffectEntityItem::integrateParticle(Particle& particle, float deltaTime) {
_particleRadiuses[index] = Interpolate::interpolate3Points(_radiusStarts[index], _radiusMiddles[index], glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * particle.acceleration;
_radiusFinishes[index], age); glm::vec3 at = particle.acceleration * deltaTime;
} particle.position += particle.velocity * deltaTime + atSquared;
particle.velocity += at;
void ParticleEffectEntityItem::updateColor(quint32 index, float age) {
_particleColors[index].red = (int)Interpolate::interpolate3Points(_colorStarts[index].red, _colorMiddles[index].red,
_colorFinishes[index].red, age);
_particleColors[index].green = (int)Interpolate::interpolate3Points(_colorStarts[index].green, _colorMiddles[index].green,
_colorFinishes[index].green, age);
_particleColors[index].blue = (int)Interpolate::interpolate3Points(_colorStarts[index].blue, _colorMiddles[index].blue,
_colorFinishes[index].blue, age);
}
void ParticleEffectEntityItem::updateAlpha(quint32 index, float age) {
_particleAlphas[index] = Interpolate::interpolate3Points(_alphaStarts[index], _alphaMiddles[index],
_alphaFinishes[index], age);
}
void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) {
_particleMinBound.x = glm::min(_particleMinBound.x, point.x);
_particleMinBound.y = glm::min(_particleMinBound.y, point.y);
_particleMinBound.z = glm::min(_particleMinBound.z, point.z);
_particleMaxBound.x = glm::max(_particleMaxBound.x, point.x);
_particleMaxBound.y = glm::max(_particleMaxBound.y, point.y);
_particleMaxBound.z = glm::max(_particleMaxBound.z, point.z);
}
void ParticleEffectEntityItem::integrateParticle(quint32 index, float deltaTime) {
glm::vec3 accel = _particleAccelerations[index];
glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * accel;
glm::vec3 at = accel * deltaTime;
_particlePositions[index] += _particleVelocities[index] * deltaTime + atSquared;
_particleVelocities[index] += at;
} }
void ParticleEffectEntityItem::stepSimulation(float deltaTime) { void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
_particleMinBound = glm::vec3(-1.0f, -1.0f, -1.0f);
_particleMaxBound = glm::vec3(1.0f, 1.0f, 1.0f);
// update particles between head and tail // update particles between head and tail
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { for (Particle& particle : _particles) {
_particleLifetimes[i] += deltaTime; particle.lifetime += deltaTime;
// if particle has died. // if particle has died.
if (_particleLifetimes[i] >= _lifespan || _lifespan < EPSILON) { if (particle.lifetime >= _lifespan) {
// move head forward // move head forward
_particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; _particles.pop_front();
} else { } else {
float age = _particleLifetimes[i] / _lifespan; // 0.0 .. 1.0 // Otherwise update it
updateRadius(i, age); integrateParticle(particle, deltaTime);
updateColor(i, age);
updateAlpha(i, age);
integrateParticle(i, deltaTime);
extendBounds(_particlePositions[i]);
} }
} }
@ -677,192 +613,104 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
float timeLeftInFrame = deltaTime; float timeLeftInFrame = deltaTime;
while (_timeUntilNextEmit < timeLeftInFrame) { while (_timeUntilNextEmit < timeLeftInFrame) {
timeLeftInFrame -= _timeUntilNextEmit;
_timeUntilNextEmit = 1.0f / _emitRate;
// emit a new particle at tail index.
quint32 i = _particleTailIndex;
_particleLifetimes[i] = 0.0f;
// Radius
if (_radiusSpread == 0.0f) {
_radiusStarts[i] = getRadiusStart();
_radiusMiddles[i] =_particleRadius;
_radiusFinishes[i] = getRadiusFinish();
} else {
float spreadMultiplier;
if (_particleRadius > 0.0f) {
spreadMultiplier = 1.0f + randFloatInRange(-1.0f, 1.0f) * _radiusSpread / _particleRadius;
} else {
spreadMultiplier = 1.0f;
}
_radiusStarts[i] =
glm::clamp(spreadMultiplier * getRadiusStart(), MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
_radiusMiddles[i] =
glm::clamp(spreadMultiplier * _particleRadius, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
_radiusFinishes[i] =
glm::clamp(spreadMultiplier * getRadiusFinish(), MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
}
updateRadius(i, 0.0f);
// Position, velocity, and acceleration
if (_polarStart == 0.0f && _polarFinish == 0.0f && _emitDimensions.z == 0.0f) {
// Emit along z-axis from position
_particlePositions[i] = getPosition();
_particleVelocities[i] =
(_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * Z_AXIS);
_particleAccelerations[i] = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;
} else {
// Emit around point or from ellipsoid
// - Distribute directions evenly around point
// - Distribute points relatively evenly over ellipsoid surface
// - Distribute points relatively evenly within ellipsoid volume
float elevationMinZ = sin(PI_OVER_TWO - _polarFinish);
float elevationMaxZ = sin(PI_OVER_TWO - _polarStart);
float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat());
float azimuth;
if (_azimuthFinish >= _azimuthStart) {
azimuth = _azimuthStart + (_azimuthFinish - _azimuthStart) * randFloat();
} else {
azimuth = _azimuthStart + (TWO_PI + _azimuthFinish - _azimuthStart) * randFloat();
}
glm::vec3 emitDirection;
if (_emitDimensions == glm::vec3()) {
// Point
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Z_AXIS;
_particlePositions[i] = getPosition();
} else {
// Ellipsoid
float radiusScale = 1.0f;
if (_emitRadiusStart < 1.0f) {
float emitRadiusStart = glm::max(_emitRadiusStart, EPSILON); // Avoid math complications at center
float randRadius =
emitRadiusStart + randFloatInRange(0.0f, MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
}
glm::vec3 radiuses = radiusScale * 0.5f * _emitDimensions;
float x = radiuses.x * glm::cos(elevation) * glm::cos(azimuth);
float y = radiuses.y * glm::cos(elevation) * glm::sin(azimuth);
float z = radiuses.z * glm::sin(elevation);
glm::vec3 emitPosition = glm::vec3(x, y, z);
emitDirection = glm::normalize(glm::vec3(
radiuses.x > 0.0f ? x / (radiuses.x * radiuses.x) : 0.0f,
radiuses.y > 0.0f ? y / (radiuses.y * radiuses.y) : 0.0f,
radiuses.z > 0.0f ? z / (radiuses.z * radiuses.z) : 0.0f
));
_particlePositions[i] = getPosition() + _emitOrientation * emitPosition;
}
_particleVelocities[i] =
(_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * emitDirection);
_particleAccelerations[i] = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;
}
integrateParticle(i, timeLeftInFrame);
extendBounds(_particlePositions[i]);
// Color
if (_colorSpread == xColor{ 0, 0, 0 }) {
_colorStarts[i] = getColorStart();
_colorMiddles[i] = getXColor();
_colorFinishes[i] = getColorFinish();
} else {
xColor startColor = getColorStart();
xColor middleColor = getXColor();
xColor finishColor = getColorFinish();
float spread = randFloatInRange(-1.0f, 1.0f);
float spreadMultiplierRed =
middleColor.red > 0 ? 1.0f + spread * (float)_colorSpread.red / (float)middleColor.red : 1.0f;
float spreadMultiplierGreen =
middleColor.green > 0 ? 1.0f + spread * (float)_colorSpread.green / (float)middleColor.green : 1.0f;
float spreadMultiplierBlue =
middleColor.blue > 0 ? 1.0f + spread * (float)_colorSpread.blue / (float)middleColor.blue : 1.0f;
_colorStarts[i].red = (int)glm::clamp(spreadMultiplierRed * (float)startColor.red, 0.0f, 255.0f);
_colorStarts[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)startColor.green, 0.0f, 255.0f);
_colorStarts[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)startColor.blue, 0.0f, 255.0f);
_colorMiddles[i].red = (int)glm::clamp(spreadMultiplierRed * (float)middleColor.red, 0.0f, 255.0f);
_colorMiddles[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)middleColor.green, 0.0f, 255.0f);
_colorMiddles[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)middleColor.blue, 0.0f, 255.0f);
_colorFinishes[i].red = (int)glm::clamp(spreadMultiplierRed * (float)finishColor.red, 0.0f, 255.0f);
_colorFinishes[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)finishColor.green, 0.0f, 255.0f);
_colorFinishes[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)finishColor.blue, 0.0f, 255.0f);
}
updateColor(i, 0.0f);
// Alpha
if (_alphaSpread == 0.0f) {
_alphaStarts[i] = getAlphaStart();
_alphaMiddles[i] = _alpha;
_alphaFinishes[i] = getAlphaFinish();
} else {
float spreadMultiplier = 1.0f + randFloatInRange(-1.0f, 1.0f) * _alphaSpread / _alpha;
_alphaStarts[i] = spreadMultiplier * getAlphaStart();
_alphaMiddles[i] = spreadMultiplier * _alpha;
_alphaFinishes[i] = spreadMultiplier * getAlphaFinish();
}
updateAlpha(i, 0.0f);
_particleTailIndex = (_particleTailIndex + 1) % _maxParticles;
// overflow! move head forward by one. // overflow! move head forward by one.
// because the case of head == tail indicates an empty array, not a full one. // because the case of head == tail indicates an empty array, not a full one.
// This can drop an existing older particle, but this is by design, newer particles are a higher priority. // This can drop an existing older particle, but this is by design, newer particles are a higher priority.
if (_particleTailIndex == _particleHeadIndex) { if (_particles.size() >= _maxParticles) {
_particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; _particles.pop_front();
} }
// emit a new particle at tail index.
_particles.push_back(createParticle());
auto particle = _particles.back();
particle.lifetime += timeLeftInFrame;
// Initialize it
integrateParticle(particle, deltaTime);
// Advance in frame
timeLeftInFrame -= _timeUntilNextEmit;
_timeUntilNextEmit = 1.0f / _emitRate;
} }
_timeUntilNextEmit -= timeLeftInFrame; _timeUntilNextEmit -= timeLeftInFrame;
} }
} }
ParticleEffectEntityItem::Particle ParticleEffectEntityItem::createParticle() {
Particle particle;
particle.seed = randFloatInRange(0.0f, 1.0f);
// Position, velocity, and acceleration
if (_polarStart == 0.0f && _polarFinish == 0.0f && _emitDimensions.z == 0.0f) {
// Emit along z-axis from position
particle.velocity = (_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * Vectors::UNIT_Z);
particle.acceleration = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;
} else {
// Emit around point or from ellipsoid
// - Distribute directions evenly around point
// - Distribute points relatively evenly over ellipsoid surface
// - Distribute points relatively evenly within ellipsoid volume
float elevationMinZ = sin(PI_OVER_TWO - _polarFinish);
float elevationMaxZ = sin(PI_OVER_TWO - _polarStart);
float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat());
float azimuth;
if (_azimuthFinish >= _azimuthStart) {
azimuth = _azimuthStart + (_azimuthFinish - _azimuthStart) * randFloat();
} else {
azimuth = _azimuthStart + (TWO_PI + _azimuthFinish - _azimuthStart) * randFloat();
}
glm::vec3 emitDirection;
if (_emitDimensions == Vectors::ZERO) {
// Point
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
} else {
// Ellipsoid
float radiusScale = 1.0f;
if (_emitRadiusStart < 1.0f) {
float emitRadiusStart = glm::max(_emitRadiusStart, EPSILON); // Avoid math complications at center
float randRadius =
emitRadiusStart + randFloatInRange(0.0f, MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
}
glm::vec3 radii = radiusScale * 0.5f * _emitDimensions;
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
float z = radii.z * glm::sin(elevation);
glm::vec3 emitPosition = glm::vec3(x, y, z);
emitDirection = glm::normalize(glm::vec3(
radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
));
particle.position = _emitOrientation * emitPosition;
}
particle.velocity = (_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * emitDirection);
particle.acceleration = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;
}
return particle;
}
void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) {
if (_maxParticles != maxParticles && MINIMUM_MAX_PARTICLES <= maxParticles && maxParticles <= MAXIMUM_MAX_PARTICLES) { if (_maxParticles != maxParticles && MINIMUM_MAX_PARTICLES <= maxParticles && maxParticles <= MAXIMUM_MAX_PARTICLES) {
_maxParticles = maxParticles; _maxParticles = maxParticles;
// TODO: try to do something smart here and preserve the state of existing particles. // Pop all the overflowing oldest particles
while (_particles.size() > _maxParticles) {
// resize vectors _particles.pop_front();
_particleLifetimes.resize(_maxParticles); }
_particlePositions.resize(_maxParticles);
_particleVelocities.resize(_maxParticles);
_particleRadiuses.resize(_maxParticles);
_radiusStarts.resize(_maxParticles);
_radiusMiddles.resize(_maxParticles);
_radiusFinishes.resize(_maxParticles);
_particleColors.resize(_maxParticles);
_colorStarts.resize(_maxParticles);
_colorMiddles.resize(_maxParticles);
_colorFinishes.resize(_maxParticles);
_particleAlphas.resize(_maxParticles);
_alphaStarts.resize(_maxParticles);
_alphaMiddles.resize(_maxParticles);
_alphaFinishes.resize(_maxParticles);
// effectively clear all particles and start emitting new ones from scratch. // effectively clear all particles and start emitting new ones from scratch.
_particleHeadIndex = 0;
_particleTailIndex = 0;
_timeUntilNextEmit = 0.0f; _timeUntilNextEmit = 0.0f;
} }
} }
// because particles are in a ring buffer, this isn't trivial
quint32 ParticleEffectEntityItem::getLivingParticleCount() const {
if (_particleTailIndex >= _particleHeadIndex) {
return _particleTailIndex - _particleHeadIndex;
} else {
return (_maxParticles - _particleHeadIndex) + _particleTailIndex;
}
}

View file

@ -11,19 +11,17 @@
#ifndef hifi_ParticleEffectEntityItem_h #ifndef hifi_ParticleEffectEntityItem_h
#define hifi_ParticleEffectEntityItem_h #define hifi_ParticleEffectEntityItem_h
#include <AnimationLoop.h> #include <deque>
#include "EntityItem.h" #include "EntityItem.h"
class ParticleEffectEntityItem : public EntityItem { class ParticleEffectEntityItem : public EntityItem {
public: public:
ALLOW_INSTANTIATION // This class can be instantiated
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
ParticleEffectEntityItem(const EntityItemID& entityItemID); ParticleEffectEntityItem(const EntityItemID& entityItemID);
virtual ~ParticleEffectEntityItem();
ALLOW_INSTANTIATION // This class can be instantiated
// methods for getting/setting all properties of this entity // methods for getting/setting all properties of this entity
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
@ -218,16 +216,27 @@ public:
virtual bool supportsDetailedRayIntersection() const { return false; } virtual bool supportsDetailedRayIntersection() const { return false; }
protected: protected:
struct Particle;
using Particles = std::deque<Particle>;
bool isAnimatingSomething() const; bool isAnimatingSomething() const;
Particle createParticle();
void stepSimulation(float deltaTime); void stepSimulation(float deltaTime);
void updateRadius(quint32 index, float age); void integrateParticle(Particle& particle, float deltaTime);
void updateColor(quint32 index, float age);
void updateAlpha(quint32 index, float age); struct Particle {
void extendBounds(const glm::vec3& point); float seed { 0.0f };
void integrateParticle(quint32 index, float deltaTime); float lifetime { 0.0f };
quint32 getLivingParticleCount() const; glm::vec3 position { Vectors::ZERO };
// the properties of this entity glm::vec3 velocity { Vectors::ZERO };
glm::vec3 acceleration { Vectors::ZERO };
};
// Particles container
Particles _particles;
// Particles properties
rgbColor _color; rgbColor _color;
xColor _colorStart = DEFAULT_COLOR; xColor _colorStart = DEFAULT_COLOR;
xColor _colorFinish = DEFAULT_COLOR; xColor _colorFinish = DEFAULT_COLOR;
@ -236,63 +245,42 @@ protected:
float _alphaStart = DEFAULT_ALPHA_START; float _alphaStart = DEFAULT_ALPHA_START;
float _alphaFinish = DEFAULT_ALPHA_FINISH; float _alphaFinish = DEFAULT_ALPHA_FINISH;
float _alphaSpread = DEFAULT_ALPHA_SPREAD; float _alphaSpread = DEFAULT_ALPHA_SPREAD;
quint32 _maxParticles = DEFAULT_MAX_PARTICLES;
float _lifespan = DEFAULT_LIFESPAN;
float _emitRate = DEFAULT_EMIT_RATE;
float _emitSpeed = DEFAULT_EMIT_SPEED;
float _speedSpread = DEFAULT_SPEED_SPREAD;
glm::quat _emitOrientation = DEFAULT_EMIT_ORIENTATION;
glm::vec3 _emitDimensions = DEFAULT_EMIT_DIMENSIONS;
float _emitRadiusStart = DEFAULT_EMIT_RADIUS_START;
float _polarStart = DEFAULT_POLAR_START;
float _polarFinish = DEFAULT_POLAR_FINISH;
float _azimuthStart = DEFAULT_AZIMUTH_START;
float _azimuthFinish = DEFAULT_AZIMUTH_FINISH;
glm::vec3 _emitAcceleration = DEFAULT_EMIT_ACCELERATION;
glm::vec3 _accelerationSpread = DEFAULT_ACCELERATION_SPREAD;
float _particleRadius = DEFAULT_PARTICLE_RADIUS; float _particleRadius = DEFAULT_PARTICLE_RADIUS;
float _radiusStart = DEFAULT_RADIUS_START; float _radiusStart = DEFAULT_RADIUS_START;
float _radiusFinish = DEFAULT_RADIUS_FINISH; float _radiusFinish = DEFAULT_RADIUS_FINISH;
float _radiusSpread = DEFAULT_RADIUS_SPREAD; float _radiusSpread = DEFAULT_RADIUS_SPREAD;
float _lifespan = DEFAULT_LIFESPAN;
// Emiter properties
quint32 _maxParticles = DEFAULT_MAX_PARTICLES;
float _emitRate = DEFAULT_EMIT_RATE;
float _emitSpeed = DEFAULT_EMIT_SPEED;
float _speedSpread = DEFAULT_SPEED_SPREAD;
glm::quat _emitOrientation = DEFAULT_EMIT_ORIENTATION;
glm::vec3 _emitDimensions = DEFAULT_EMIT_DIMENSIONS;
float _emitRadiusStart = DEFAULT_EMIT_RADIUS_START;
glm::vec3 _emitAcceleration = DEFAULT_EMIT_ACCELERATION;
glm::vec3 _accelerationSpread = DEFAULT_ACCELERATION_SPREAD;
float _polarStart = DEFAULT_POLAR_START;
float _polarFinish = DEFAULT_POLAR_FINISH;
float _azimuthStart = DEFAULT_AZIMUTH_START;
float _azimuthFinish = DEFAULT_AZIMUTH_FINISH;
quint64 _lastSimulated { 0 };
bool _isEmitting { true };
quint64 _lastSimulated; QString _textures { DEFAULT_TEXTURES };
bool _isEmitting = true; bool _texturesChangedFlag { false };
ShapeType _shapeType { SHAPE_TYPE_NONE };
float _timeUntilNextEmit { 0.0f };
QString _textures = DEFAULT_TEXTURES;
bool _texturesChangedFlag = false; bool _additiveBlending { DEFAULT_ADDITIVE_BLENDING };
ShapeType _shapeType = SHAPE_TYPE_NONE;
// all the internals of running the particle sim
QVector<float> _particleLifetimes;
QVector<glm::vec3> _particlePositions;
QVector<glm::vec3> _particleVelocities;
QVector<glm::vec3> _particleAccelerations;
QVector<float> _particleRadiuses;
QVector<float> _radiusStarts;
QVector<float> _radiusMiddles;
QVector<float> _radiusFinishes;
QVector<xColor> _particleColors;
QVector<xColor> _colorStarts;
QVector<xColor> _colorMiddles;
QVector<xColor> _colorFinishes;
QVector<float> _particleAlphas;
QVector<float> _alphaStarts;
QVector<float> _alphaMiddles;
QVector<float> _alphaFinishes;
float _timeUntilNextEmit = 0.0f;
// particle arrays are a ring buffer, use these indices
// to keep track of the living particles.
quint32 _particleHeadIndex = 0;
quint32 _particleTailIndex = 0;
// bounding volume
glm::vec3 _particleMaxBound;
glm::vec3 _particleMinBound;
bool _additiveBlending;
}; };
#endif // hifi_ParticleEffectEntityItem_h #endif // hifi_ParticleEffectEntityItem_h

View file

@ -57,7 +57,7 @@ public:
static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; }
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
virtual bool isReadyToComputeShape() { return false; } virtual bool isReadyToComputeShape() { return true; }
void updateShapeType(ShapeType type) { _shapeType = type; } void updateShapeType(ShapeType type) { _shapeType = type; }
virtual ShapeType getShapeType() const; virtual ShapeType getShapeType() const;

View file

@ -49,35 +49,32 @@ template <
> >
class GLEscrow { class GLEscrow {
public: public:
static const uint64_t MAX_UNSIGNALED_TIME = USECS_PER_SECOND / 2;
struct Item { struct Item {
T _value; const T _value;
GLsync _sync; GLsync _sync;
uint64_t _created; const uint64_t _created;
Item(T value, GLsync sync) : Item(T value, GLsync sync) :
_value(value), _sync(sync), _created(usecTimestampNow()) _value(value), _sync(sync), _created(usecTimestampNow())
{ {
} }
uint64_t age() { uint64_t age() const {
return usecTimestampNow() - _created; return usecTimestampNow() - _created;
} }
bool signaled() { bool signaled() const {
auto result = glClientWaitSync(_sync, 0, 0); auto result = glClientWaitSync(_sync, 0, 0);
if (GL_TIMEOUT_EXPIRED != result && GL_WAIT_FAILED != result) { if (GL_TIMEOUT_EXPIRED != result && GL_WAIT_FAILED != result) {
return true; return true;
} }
if (age() > (USECS_PER_SECOND / 2)) {
qWarning() << "Long unsignaled sync";
}
return false; return false;
} }
}; };
using Mutex = std::recursive_mutex; using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
using Recycler = std::function<void(T t)>; using Recycler = std::function<void(T t)>;
// deque gives us random access, double ended push & pop and size, all in constant time // deque gives us random access, double ended push & pop and size, all in constant time
using Deque = std::deque<Item>; using Deque = std::deque<Item>;
@ -87,9 +84,32 @@ public:
_recycler = recycler; _recycler = recycler;
} }
size_t depth() { template <typename F>
void withLock(F f) {
using Lock = std::unique_lock<Mutex>;
Lock lock(_mutex); Lock lock(_mutex);
return _submits.size(); f();
}
template <typename F>
bool tryLock(F f) {
using Lock = std::unique_lock<Mutex>;
bool result = false;
Lock lock(_mutex, std::try_to_lock_t());
if (lock.owns_lock()) {
f();
result = true;
}
return result;
}
size_t depth() {
size_t result{ 0 };
withLock([&]{
result = _submits.size();
});
return result;
} }
// Submit a new resource from the producer context // Submit a new resource from the producer context
@ -104,11 +124,9 @@ public:
glFlush(); glFlush();
} }
{ withLock([&]{
Lock lock(_mutex);
_submits.push_back(Item(t, writeSync)); _submits.push_back(Item(t, writeSync));
} });
return cleanTrash(); return cleanTrash();
} }
@ -120,13 +138,13 @@ public:
// On the one hand using try_lock() reduces the chance of blocking the consumer thread, // On the one hand using try_lock() reduces the chance of blocking the consumer thread,
// but if the produce thread is going fast enough, it could effectively // but if the produce thread is going fast enough, it could effectively
// starve the consumer out of ever actually getting resources. // starve the consumer out of ever actually getting resources.
if (_mutex.try_lock()) { tryLock([&]{
// May be called on any thread, but must be inside a locked section
if (signaled(_submits, 0)) { if (signaled(_submits, 0)) {
result = _submits.at(0)._value; result = _submits.at(0)._value;
_submits.pop_front(); _submits.pop_front();
} }
_mutex.unlock(); });
}
return result; return result;
} }
@ -154,37 +172,45 @@ public:
glFlush(); glFlush();
} }
Lock lock(_mutex); withLock([&]{
_releases.push_back(Item(t, readSync)); _releases.push_back(Item(t, readSync));
});
} }
private: private:
size_t cleanTrash() { size_t cleanTrash() {
size_t wastedWork{ 0 }; size_t wastedWork{ 0 };
List trash; List trash;
{ tryLock([&]{
while (!_submits.empty()) {
const auto& item = _submits.front();
if (!item._sync || item.age() < MAX_UNSIGNALED_TIME) {
break;
}
qWarning() << "Long unsignaled sync " << item._sync << " unsignaled for " << item.age();
_trash.push_front(item);
_submits.pop_front();
}
// We only ever need one ready item available in the list, so if the // We only ever need one ready item available in the list, so if the
// second item is signaled (implying the first is as well, remove the first // second item is signaled (implying the first is as well, remove the first
// item. Iterate until the SECOND item in the list is not in the ready state // item. Iterate until the SECOND item in the list is not in the ready state
// The signaled function takes care of checking against the deque size // The signaled function takes care of checking against the deque size
while (signaled(_submits, 1)) { while (signaled(_submits, 1)) {
pop(_submits); _trash.push_front(_submits.front());
_submits.pop_front();
++wastedWork; ++wastedWork;
} }
// Stuff in the release queue can be cleared out as soon as it's signaled // Stuff in the release queue can be cleared out as soon as it's signaled
while (signaled(_releases, 0)) { while (signaled(_releases, 0)) {
pop(_releases); _trash.push_front(_releases.front());
_releases.pop_front();
} }
{ trash.swap(_trash);
// FIXME I don't think this lock should be necessary, only the submitting thread });
// touches the trash
Lock lock(_mutex);
trash.swap(_trash);
}
}
// FIXME maybe doing a timing on the deleters and warn if it's taking excessive time? // FIXME maybe doing a timing on the deleters and warn if it's taking excessive time?
// although we are out of the lock, so it shouldn't be blocking anything // although we are out of the lock, so it shouldn't be blocking anything
std::for_each(trash.begin(), trash.end(), [&](typename List::const_reference item) { std::for_each(trash.begin(), trash.end(), [&](typename List::const_reference item) {
@ -198,14 +224,6 @@ private:
return wastedWork; return wastedWork;
} }
// May be called on any thread, but must be inside a locked section
void pop(Deque& deque) {
Lock lock(_mutex);
auto& item = deque.front();
_trash.push_front(item);
deque.pop_front();
}
// May be called on any thread, but must be inside a locked section // May be called on any thread, but must be inside a locked section
bool signaled(Deque& deque, size_t i) { bool signaled(Deque& deque, size_t i) {
if (i >= deque.size()) { if (i >= deque.size()) {

View file

@ -8,16 +8,18 @@
#include "OffscreenQmlSurface.h" #include "OffscreenQmlSurface.h"
#include "OglplusHelpers.h" #include "OglplusHelpers.h"
#include <QWidget> #include <QtWidgets/QWidget>
#include <QtQml> #include <QtQml/QtQml>
#include <QQmlEngine> #include <QtQml/QQmlEngine>
#include <QQmlComponent> #include <QtQml/QQmlComponent>
#include <QQuickItem> #include <QtQuick/QQuickItem>
#include <QQuickWindow> #include <QtQuick/QQuickWindow>
#include <QQuickRenderControl> #include <QtQuick/QQuickRenderControl>
#include <QWaitCondition> #include <QtCore/QWaitCondition>
#include <QMutex> #include <QtCore/QMutex>
#include <QtGui/QOpenGLContext>
#include <shared/NsightHelpers.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
@ -25,8 +27,6 @@
#include "GLEscrow.h" #include "GLEscrow.h"
#include "OffscreenGLCanvas.h" #include "OffscreenGLCanvas.h"
// FIXME move to threaded rendering with Qt 5.5
//#define QML_THREADED
// Time between receiving a request to render the offscreen UI actually triggering // Time between receiving a request to render the offscreen UI actually triggering
// the render. Could possibly be increased depending on the framerate we expect to // the render. Could possibly be increased depending on the framerate we expect to
@ -56,13 +56,11 @@ private:
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
#ifdef QML_THREADED
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1); static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3); static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5); static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
#endif
class OffscreenQmlRenderer : public OffscreenGLCanvas { class OffscreenQmlRenderer : public OffscreenGLCanvas {
friend class OffscreenQmlSurface; friend class OffscreenQmlSurface;
@ -70,22 +68,30 @@ public:
OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
OffscreenGLCanvas::create(shareContext); OffscreenGLCanvas::create(shareContext);
#ifdef QML_THREADED
_renderControl = new QMyQuickRenderControl();
// Create a QQuickWindow that is associated with out render control. Note that this
// window never gets created or shown, meaning that it will never get an underlying
// native (platform) window.
QQuickWindow::setDefaultAlphaBuffer(true);
// Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render
// because it retains an internal 'context' object that retains the thread it was created on,
// regardless of whether you later move it to another thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
// Qt 5.5 // Qt 5.5
_renderControl->prepareThread(_renderThread); _renderControl->prepareThread(&_thread);
_context->moveToThread(&_thread); _context->moveToThread(&_thread);
moveToThread(&_thread); moveToThread(&_thread);
_thread.setObjectName("QML Thread"); _thread.setObjectName("QML Thread");
_thread.start(); _thread.start();
post(INIT); post(INIT);
#else
init();
#endif
} }
#ifdef QML_THREADED bool event(QEvent *e) {
bool event(QEvent *e)
{
switch (int(e->type())) { switch (int(e->type())) {
case INIT: case INIT:
{ {
@ -120,7 +126,6 @@ public:
QCoreApplication::postEvent(this, new QEvent(type)); QCoreApplication::postEvent(this, new QEvent(type));
} }
#endif
private: private:
@ -143,27 +148,9 @@ private:
void init() { void init() {
_renderControl = new QMyQuickRenderControl();
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
// Create a QQuickWindow that is associated with out render control. Note that this
// window never gets created or shown, meaning that it will never get an underlying
// native (platform) window.
QQuickWindow::setDefaultAlphaBuffer(true);
// Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render
// because it retains an internal 'context' object that retains the thread it was created on,
// regardless of whether you later move it to another thread.
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
#ifdef QML_THREADED
// However, because we want to use synchronous events with the quickwindow, we need to move it back to the main
// thread after it's created.
_quickWindow->moveToThread(qApp->thread());
#endif
if (!makeCurrent()) { if (!makeCurrent()) {
qWarning("Failed to make context current on render thread"); qWarning("Failed to make context current on render thread");
return; return;
@ -189,17 +176,15 @@ private:
doneCurrent(); doneCurrent();
#ifdef QML_THREADED
_context->moveToThread(QCoreApplication::instance()->thread()); _context->moveToThread(QCoreApplication::instance()->thread());
_cond.wakeOne(); _cond.wakeOne();
#endif
} }
void resize(const QSize& newSize) { void resize() {
// Update our members // Update our members
if (_quickWindow) { if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize)); _quickWindow->setGeometry(QRect(QPoint(), _newSize));
_quickWindow->contentItem()->setSize(newSize); _quickWindow->contentItem()->setSize(_newSize);
} }
// Qt bug in 5.4 forces this check of pixel ratio, // Qt bug in 5.4 forces this check of pixel ratio,
@ -209,7 +194,7 @@ private:
pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
} }
uvec2 newOffscreenSize = toGlm(newSize * pixelRatio); uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
_textures.setSize(newOffscreenSize); _textures.setSize(newOffscreenSize);
if (newOffscreenSize == _size) { if (newOffscreenSize == _size) {
return; return;
@ -222,7 +207,7 @@ private:
return; return;
} }
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; qDebug() << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
setupFbo(); setupFbo();
doneCurrent(); doneCurrent();
} }
@ -237,54 +222,44 @@ private:
return; return;
} }
//Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size);
//Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _textures._size);
_renderControl->sync(); _renderControl->sync();
#ifdef QML_THREADED
_cond.wakeOne(); _cond.wakeOne();
lock->unlock(); lock->unlock();
#endif
using namespace oglplus; using namespace oglplus;
_quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y)); _quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y));
TexturePtr texture = _textures.getNextTexture();
_fbo->Bind(Framebuffer::Target::Draw);
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
_fbo->Complete(Framebuffer::Target::Draw);
//Context::Clear().ColorBuffer();
{ {
_renderControl->render(); PROFILE_RANGE("qml_render")
// FIXME The web browsers seem to be leaving GL in an error state. TexturePtr texture = _textures.getNextTexture();
// Need a debug context with sync logging to figure out why. _fbo->Bind(Framebuffer::Target::Draw);
// for now just clear the errors _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
glGetError(); _fbo->Complete(Framebuffer::Target::Draw);
{
_renderControl->render();
// FIXME The web browsers seem to be leaving GL in an error state.
// Need a debug context with sync logging to figure out why.
// for now just clear the errors
glGetError();
}
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();
_escrow.submit(GetName(*texture));
} }
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();
_escrow.submit(GetName(*texture));
_lastRenderTime = usecTimestampNow(); _lastRenderTime = usecTimestampNow();
} }
void aboutToQuit() { void aboutToQuit() {
#ifdef QML_THREADED
QMutexLocker lock(&_quitMutex); QMutexLocker lock(&_quitMutex);
_quit = true; _quit = true;
#endif
} }
void stop() { void stop() {
#ifdef QML_THREADED QMutexLocker lock(&_mutex);
QMutexLocker lock(&_quitMutex);
post(STOP); post(STOP);
_cond.wait(&_mutex); _cond.wait(&_mutex);
#else
cleanup();
#endif
} }
bool allowNewFrame(uint8_t fps) { bool allowNewFrame(uint8_t fps) {
@ -297,13 +272,12 @@ private:
QQuickWindow* _quickWindow{ nullptr }; QQuickWindow* _quickWindow{ nullptr };
QMyQuickRenderControl* _renderControl{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr };
#ifdef QML_THREADED
QThread _thread; QThread _thread;
QMutex _mutex; QMutex _mutex;
QWaitCondition _cond; QWaitCondition _cond;
QMutex _quitMutex; QMutex _quitMutex;
#endif
QSize _newSize;
bool _quit; bool _quit;
FramebufferPtr _fbo; FramebufferPtr _fbo;
RenderbufferPtr _depthStencil; RenderbufferPtr _depthStencil;
@ -346,9 +320,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
} }
void OffscreenQmlSurface::resize(const QSize& newSize) { void OffscreenQmlSurface::resize(const QSize& newSize) {
#ifdef QML_THREADED
QMutexLocker _locker(&(_renderer->_mutex));
#endif
if (!_renderer || !_renderer->_quickWindow) { if (!_renderer || !_renderer->_quickWindow) {
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) { if (newSize == currentSize) {
@ -362,11 +334,12 @@ void OffscreenQmlSurface::resize(const QSize& newSize) {
_rootItem->setSize(newSize); _rootItem->setSize(newSize);
} }
#ifdef QML_THREADED {
QMutexLocker _locker(&(_renderer->_mutex));
_renderer->_newSize = newSize;
}
_renderer->post(RESIZE); _renderer->post(RESIZE);
#else
_renderer->resize(newSize);
#endif
} }
QQuickItem* OffscreenQmlSurface::getRootItem() { QQuickItem* OffscreenQmlSurface::getRootItem() {
@ -466,11 +439,7 @@ void OffscreenQmlSurface::updateQuick() {
} }
if (_render) { if (_render) {
#ifdef QML_THREADED
_renderer->post(RENDER); _renderer->post(RENDER);
#else
_renderer->render(nullptr);
#endif
_render = false; _render = false;
} }

View file

@ -16,13 +16,6 @@
#if defined(NSIGHT_FOUND) #if defined(NSIGHT_FOUND)
#include "nvToolsExt.h" #include "nvToolsExt.h"
ProfileRange::ProfileRange(const char *name) {
nvtxRangePush(name);
}
ProfileRange::~ProfileRange() {
nvtxRangePop();
}
ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) { ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) {
_batch.pushProfileRange(name); _batch.pushProfileRange(name);
} }

View file

@ -15,6 +15,8 @@
#include <mutex> #include <mutex>
#include <functional> #include <functional>
#include <shared/NsightHelpers.h>
#include "Framebuffer.h" #include "Framebuffer.h"
#include "Pipeline.h" #include "Pipeline.h"
#include "Query.h" #include "Query.h"
@ -22,18 +24,6 @@
#include "Texture.h" #include "Texture.h"
#include "Transform.h" #include "Transform.h"
#if defined(NSIGHT_FOUND)
class ProfileRange {
public:
ProfileRange(const char *name);
~ProfileRange();
};
#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
#else
#define PROFILE_RANGE(name)
#endif
class QDebug; class QDebug;
namespace gpu { namespace gpu {

View file

@ -154,7 +154,7 @@ GLBackend::GLShader* compileShader(const Shader& shader) {
qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:"; qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:";
qCWarning(gpulogging) << temp; qCWarning(gpulogging) << temp;
/* /*
filestream.open("debugshader.glsl.info.txt"); filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) { if (filestream.is_open()) {

View file

@ -77,16 +77,6 @@ bool Stream::Format::setAttribute(Slot slot, Slot channel, Frequency frequency)
return true; return true;
} }
BufferStream::BufferStream() :
_buffers(),
_offsets(),
_strides()
{}
BufferStream::~BufferStream() {
}
void BufferStream::addBuffer(const BufferPointer& buffer, Offset offset, Offset stride) { void BufferStream::addBuffer(const BufferPointer& buffer, Offset offset, Offset stride) {
_buffers.push_back(buffer); _buffers.push_back(buffer);
_offsets.push_back(offset); _offsets.push_back(offset);

View file

@ -50,7 +50,7 @@ public:
// Frequency describer // Frequency describer
enum Frequency { enum Frequency {
PER_VERTEX = 0, PER_VERTEX = 0,
PER_INSTANCE, PER_INSTANCE = 1,
}; };
// The attribute description // The attribute description
@ -106,6 +106,7 @@ public:
bool setAttribute(Slot slot, Frequency frequency = PER_VERTEX); bool setAttribute(Slot slot, Frequency frequency = PER_VERTEX);
bool setAttribute(Slot slot, Slot channel, Frequency frequency = PER_VERTEX); bool setAttribute(Slot slot, Slot channel, Frequency frequency = PER_VERTEX);
bool hasAttribute(Slot slot) const { return (_attributes.find(slot) != _attributes.end()); }
protected: protected:
AttributeMap _attributes; AttributeMap _attributes;
@ -124,10 +125,7 @@ typedef std::vector< Offset > Offsets;
// A Buffer Stream can be assigned to the Batch to set several stream channels in one call // A Buffer Stream can be assigned to the Batch to set several stream channels in one call
class BufferStream { class BufferStream {
public: public:
typedef Offsets Strides; using Strides = Offsets;
BufferStream();
~BufferStream();
void clear() { _buffers.clear(); _offsets.clear(); _strides.clear(); } void clear() { _buffers.clear(); _offsets.clear(); _strides.clear(); }
void addBuffer(const BufferPointer& buffer, Offset offset, Offset stride); void addBuffer(const BufferPointer& buffer, Offset offset, Offset stride);

View file

@ -86,7 +86,6 @@ TransformCamera getTransformCamera() {
} }
<@endfunc@> <@endfunc@>
<@func transformModelToWorldPos(objectTransform, modelPos, worldPos)@> <@func transformModelToWorldPos(objectTransform, modelPos, worldPos)@>
{ // transformModelToWorldPos { // transformModelToWorldPos
<$worldPos$> = (<$objectTransform$>._model * <$modelPos$>); <$worldPos$> = (<$objectTransform$>._model * <$modelPos$>);
@ -136,7 +135,22 @@ TransformCamera getTransformCamera() {
<@func transformClipToEyeDir(cameraTransform, clipPos, eyeDir)@> <@func transformClipToEyeDir(cameraTransform, clipPos, eyeDir)@>
{ // transformClipToEyeDir { // transformClipToEyeDir
<$eyeDir$> = vec3(<$cameraTransform$>._projectionInverse * vec4(<$clipPos$>.xyz, 1.0)); <$eyeDir$> = vec3(<$cameraTransform$>._projectionInverse * vec4(<$clipPos$>.xyz, 1.0)); // Must be 1.0 here
}
<@endfunc@>
<@func $transformModelToEyePos(cameraTransform, objectTransform, modelPos, eyePos)@>
<!// Equivalent to the following but hoppefully a tad more accurate
//return camera._view * object._model * pos; !>
{ // transformModelToEyePos
vec4 _worldpos = (<$objectTransform$>._model * vec4(<$modelPos$>.xyz, 1.0));
<$eyePos$> = (<$cameraTransform$>._view * _worldpos);
}
<@endfunc@>
<@func transformEyeToClipPos(cameraTransform, eyePos, clipPos)@>
{ // transformEyeToClipPos
<$clipPos$> = <$cameraTransform$>._projection * vec4(<$eyePos$>.xyz, 1.0);
} }
<@endfunc@> <@endfunc@>

View file

@ -1,15 +1,5 @@
set(TARGET_NAME input-plugins) set(TARGET_NAME input-plugins)
setup_hifi_library() setup_hifi_library()
link_hifi_libraries(shared plugins controllers script-engine render-utils) link_hifi_libraries(shared plugins controllers)
GroupSources("src/input-plugins") GroupSources("src/input-plugins")
if (WIN32)
add_dependency_external_projects(OpenVR)
find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
endif()
target_sdl2()
target_sixense()

View file

@ -13,17 +13,11 @@
#include <plugins/PluginManager.h> #include <plugins/PluginManager.h>
#include "KeyboardMouseDevice.h" #include "KeyboardMouseDevice.h"
#include "SDL2Manager.h"
#include "SixenseManager.h"
#include "ViveControllerManager.h"
// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
InputPluginList getInputPlugins() { InputPluginList getInputPlugins() {
InputPlugin* PLUGIN_POOL[] = { InputPlugin* PLUGIN_POOL[] = {
new KeyboardMouseDevice(), new KeyboardMouseDevice(),
new SDL2Manager(),
new SixenseManager(),
new ViveControllerManager(),
nullptr nullptr
}; };

View file

@ -92,7 +92,7 @@ void Mesh::setPartBuffer(const BufferView& buffer) {
_partBuffer = buffer; _partBuffer = buffer;
} }
const Box Mesh::evalPartBound(int partNum) const { Box Mesh::evalPartBound(int partNum) const {
Box box; Box box;
if (partNum < _partBuffer.getNum<Part>()) { if (partNum < _partBuffer.getNum<Part>()) {
const Part& part = _partBuffer.get<Part>(partNum); const Part& part = _partBuffer.get<Part>(partNum);
@ -111,7 +111,7 @@ const Box Mesh::evalPartBound(int partNum) const {
return box; return box;
} }
const Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const { Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const {
Box totalBound; Box totalBound;
auto part = _partBuffer.cbegin<Part>() + partStart; auto part = _partBuffer.cbegin<Part>() + partStart;
auto partItEnd = _partBuffer.cbegin<Part>() + partEnd; auto partItEnd = _partBuffer.cbegin<Part>() + partEnd;

View file

@ -107,10 +107,10 @@ public:
uint getNumParts() const { return _partBuffer.getNumElements(); } uint getNumParts() const { return _partBuffer.getNumElements(); }
// evaluate the bounding box of A part // evaluate the bounding box of A part
const Box evalPartBound(int partNum) const; Box evalPartBound(int partNum) const;
// evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter // evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter
// the returned box is the bounding box of ALL the evaluated part bounds. // the returned box is the bounding box of ALL the evaluated part bounds.
const Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const; Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const;
static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast<gpu::Primitive>(topo); } static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast<gpu::Primitive>(topo); }

View file

@ -18,7 +18,7 @@ Light::Light() :
_transform() { _transform() {
// only if created from nothing shall we create the Buffer to store the properties // only if created from nothing shall we create the Buffer to store the properties
Schema schema; Schema schema;
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema)); _schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
} }
Light::Light(const Light& light) : Light::Light(const Light& light) :

View file

@ -112,8 +112,6 @@ public:
Vec4 _shadow{0.0f}; Vec4 _shadow{0.0f};
Vec4 _control{0.0f, 0.0f, 0.0f, 0.0f}; Vec4 _control{0.0f, 0.0f, 0.0f, 0.0f};
Schema() {}
}; };
const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; }

View file

@ -71,22 +71,12 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum) const {
void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) {
// Create the static shared elements used to render the skybox // Create the static shared elements used to render the skybox
static gpu::BufferPointer theBuffer;
static gpu::Stream::FormatPointer theFormat;
static gpu::BufferPointer theConstants; static gpu::BufferPointer theConstants;
static gpu::PipelinePointer thePipeline; static gpu::PipelinePointer thePipeline;
const int SKYBOX_SKYMAP_SLOT = 0; const int SKYBOX_SKYMAP_SLOT = 0;
const int SKYBOX_CONSTANTS_SLOT = 0; const int SKYBOX_CONSTANTS_SLOT = 0;
static std::once_flag once; static std::once_flag once;
std::call_once(once, [&] { std::call_once(once, [&] {
{
const float CLIP = 1.0f;
const glm::vec2 vertices[4] = { { -CLIP, -CLIP }, { CLIP, -CLIP }, { -CLIP, CLIP }, { CLIP, CLIP } };
theBuffer = std::make_shared<gpu::Buffer>(sizeof(vertices), (const gpu::Byte*) vertices);
theFormat = std::make_shared<gpu::Stream::Format>();
theFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ));
}
{ {
auto skyVS = gpu::Shader::createVertex(std::string(Skybox_vert)); auto skyVS = gpu::Shader::createVertex(std::string(Skybox_vert));
auto skyFS = gpu::Shader::createPixel(std::string(Skybox_frag)); auto skyFS = gpu::Shader::createPixel(std::string(Skybox_frag));
@ -115,8 +105,6 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky
batch.setProjectionTransform(projMat); batch.setProjectionTransform(projMat);
batch.setViewTransform(viewTransform); batch.setViewTransform(viewTransform);
batch.setModelTransform(Transform()); // only for Mac batch.setModelTransform(Transform()); // only for Mac
batch.setInputBuffer(gpu::Stream::POSITION, theBuffer, 0, 8);
batch.setInputFormat(theFormat);
gpu::TexturePointer skymap; gpu::TexturePointer skymap;
if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) {

View file

@ -11,21 +11,26 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
<@include gpu/Inputs.slh@>
<@include gpu/Transform.slh@> <@include gpu/Transform.slh@>
<$declareStandardTransform()$> <$declareStandardTransform()$>
out vec3 _normal; out vec3 _normal;
void main(void) { void main(void) {
const float depth = 0.0;
const vec4 UNIT_QUAD[4] = vec4[4](
vec4(-1.0, -1.0, depth, 1.0),
vec4(1.0, -1.0, depth, 1.0),
vec4(-1.0, 1.0, depth, 1.0),
vec4(1.0, 1.0, depth, 1.0)
);
vec4 inPosition = UNIT_QUAD[gl_VertexID];
// standard transform // standard transform
TransformCamera cam = getTransformCamera(); TransformCamera cam = getTransformCamera();
vec3 clipDir = vec3(inPosition.xy, 0.0); vec3 clipDir = vec3(inPosition.xy, 0.0);
vec3 eyeDir; vec3 eyeDir;
<$transformClipToEyeDir(cam, clipDir, eyeDir)$> <$transformClipToEyeDir(cam, clipDir, eyeDir)$>
<$transformEyeToWorldDir(cam, eyeDir, _normal)$> <$transformEyeToWorldDir(cam, eyeDir, _normal)$>

View file

@ -296,8 +296,6 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
if (matchingNode) { if (matchingNode) {
emit dataReceived(matchingNode->getType(), receivedMessage->getSize()); emit dataReceived(matchingNode->getType(), receivedMessage->getSize());
matchingNode->recordBytesReceived(receivedMessage->getSize()); matchingNode->recordBytesReceived(receivedMessage->getSize());
Node* n = matchingNode.data();
auto addr = n->getActiveSocket();
QMetaMethod metaMethod = listener.method; QMetaMethod metaMethod = listener.method;

View file

@ -22,8 +22,8 @@ static const int HEAD_DATA_SIZE = 512;
ReceivedMessage::ReceivedMessage(const NLPacketList& packetList) ReceivedMessage::ReceivedMessage(const NLPacketList& packetList)
: _data(packetList.getMessage()), : _data(packetList.getMessage()),
_headData(_data.mid(0, HEAD_DATA_SIZE)), _headData(_data.mid(0, HEAD_DATA_SIZE)),
_sourceID(packetList.getSourceID()),
_numPackets(packetList.getNumPackets()), _numPackets(packetList.getNumPackets()),
_sourceID(packetList.getSourceID()),
_packetType(packetList.getType()), _packetType(packetList.getType()),
_packetVersion(packetList.getVersion()), _packetVersion(packetList.getVersion()),
_senderSockAddr(packetList.getSenderSockAddr()), _senderSockAddr(packetList.getSenderSockAddr()),
@ -34,8 +34,8 @@ ReceivedMessage::ReceivedMessage(const NLPacketList& packetList)
ReceivedMessage::ReceivedMessage(NLPacket& packet) ReceivedMessage::ReceivedMessage(NLPacket& packet)
: _data(packet.readAll()), : _data(packet.readAll()),
_headData(_data.mid(0, HEAD_DATA_SIZE)), _headData(_data.mid(0, HEAD_DATA_SIZE)),
_sourceID(packet.getSourceID()),
_numPackets(1), _numPackets(1),
_sourceID(packet.getSourceID()),
_packetType(packet.getType()), _packetType(packet.getType()),
_packetVersion(packet.getVersion()), _packetVersion(packet.getVersion()),
_senderSockAddr(packet.getSenderSockAddr()), _senderSockAddr(packet.getSenderSockAddr()),

View file

@ -319,10 +319,10 @@ void Resource::attemptRequest() {
void Resource::finishedLoading(bool success) { void Resource::finishedLoading(bool success) {
if (success) { if (success) {
qDebug().noquote() << "Finished loading:" << _url.toDisplayString(); qCDebug(networking).noquote() << "Finished loading:" << _url.toDisplayString();
_loaded = true; _loaded = true;
} else { } else {
qDebug().noquote() << "Failed to load:" << _url.toDisplayString(); qCDebug(networking).noquote() << "Failed to load:" << _url.toDisplayString();
_failedToLoad = true; _failedToLoad = true;
} }
_loadPriorities.clear(); _loadPriorities.clear();
@ -339,13 +339,13 @@ void Resource::makeRequest() {
_request = ResourceManager::createResourceRequest(this, _activeUrl); _request = ResourceManager::createResourceRequest(this, _activeUrl);
if (!_request) { if (!_request) {
qDebug().noquote() << "Failed to get request for" << _url.toDisplayString(); qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
ResourceCache::requestCompleted(this); ResourceCache::requestCompleted(this);
finishedLoading(false); finishedLoading(false);
return; return;
} }
qDebug().noquote() << "Starting request for:" << _url.toDisplayString(); qCDebug(networking).noquote() << "Starting request for:" << _url.toDisplayString();
connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress);
connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished);
@ -369,7 +369,7 @@ void Resource::handleReplyFinished() {
if (result == ResourceRequest::Success) { if (result == ResourceRequest::Success) {
_data = _request->getData(); _data = _request->getData();
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qDebug().noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo); qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
finishedLoading(true); finishedLoading(true);
emit loaded(_data); emit loaded(_data);
@ -377,7 +377,7 @@ void Resource::handleReplyFinished() {
} else { } else {
switch (result) { switch (result) {
case ResourceRequest::Result::Timeout: { case ResourceRequest::Result::Timeout: {
qDebug() << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
// Fall through to other cases // Fall through to other cases
} }
case ResourceRequest::Result::ServerUnavailable: { case ResourceRequest::Result::ServerUnavailable: {
@ -386,7 +386,7 @@ void Resource::handleReplyFinished() {
const int BASE_DELAY_MS = 1000; const int BASE_DELAY_MS = 1000;
if (_attempts++ < MAX_ATTEMPTS) { if (_attempts++ < MAX_ATTEMPTS) {
auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts); auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts);
qDebug().nospace() << "Retrying to load the asset in " << waitTime qCDebug(networking).nospace() << "Retrying to load the asset in " << waitTime
<< "ms, attempt " << _attempts << " of " << MAX_ATTEMPTS; << "ms, attempt " << _attempts << " of " << MAX_ATTEMPTS;
QTimer::singleShot(waitTime, this, &Resource::attemptRequest); QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
break; break;
@ -394,7 +394,7 @@ void Resource::handleReplyFinished() {
// fall through to final failure // fall through to final failure
} }
default: { default: {
qDebug() << "Error loading " << _url; qCDebug(networking) << "Error loading " << _url;
auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
: QNetworkReply::UnknownNetworkError; : QNetworkReply::UnknownNetworkError;
emit failed(error); emit failed(error);

View file

@ -41,7 +41,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd: case PacketType::EntityAdd:
case PacketType::EntityEdit: case PacketType::EntityEdit:
case PacketType::EntityData: case PacketType::EntityData:
return VERSION_ENTITIES_HAVE_PARENTS; return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP;
case PacketType::AvatarData: case PacketType::AvatarData:
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
return 17; return 17;

View file

@ -161,5 +161,6 @@ const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS = 48;
const PacketVersion VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING = 49; const PacketVersion VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING = 49;
const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50; const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50;
const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51;
const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52;
#endif // hifi_PacketHeaders_h #endif // hifi_PacketHeaders_h

View file

@ -56,6 +56,7 @@ void OctreeHeadlessViewer::queryOctree() {
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); _octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
_octreeQuery.setOctreeSizeScale(getVoxelSizeScale()); _octreeQuery.setOctreeSizeScale(getVoxelSizeScale());
_octreeQuery.setBoundaryLevelAdjust(getBoundaryLevelAdjust()); _octreeQuery.setBoundaryLevelAdjust(getBoundaryLevelAdjust());

View file

@ -63,6 +63,9 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
// desired boundaryLevelAdjust // desired boundaryLevelAdjust
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
destinationBuffer += sizeof(_boundaryLevelAdjust); destinationBuffer += sizeof(_boundaryLevelAdjust);
memcpy(destinationBuffer, &_keyholeRadius, sizeof(_keyholeRadius));
destinationBuffer += sizeof(_keyholeRadius);
return destinationBuffer - bufferStart; return destinationBuffer - bufferStart;
} }
@ -104,6 +107,12 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust)); memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
sourceBuffer += sizeof(_boundaryLevelAdjust); sourceBuffer += sizeof(_boundaryLevelAdjust);
auto bytesRead = sourceBuffer - startPosition;
auto bytesLeft = message.getSize() - bytesRead;
if (bytesLeft >= sizeof(_keyholeRadius)) {
memcpy(&_keyholeRadius, sourceBuffer, sizeof(_keyholeRadius));
sourceBuffer += sizeof(_keyholeRadius);
}
return sourceBuffer - startPosition; return sourceBuffer - startPosition;
} }

View file

@ -58,6 +58,7 @@ public:
float getCameraNearClip() const { return _cameraNearClip; } float getCameraNearClip() const { return _cameraNearClip; }
float getCameraFarClip() const { return _cameraFarClip; } float getCameraFarClip() const { return _cameraFarClip; }
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; } const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
float getKeyholeRadius() const { return _keyholeRadius; }
glm::vec3 calculateCameraDirection() const; glm::vec3 calculateCameraDirection() const;
@ -69,6 +70,7 @@ public:
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; } void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; } void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
void setKeyholeRadius(float keyholeRadius) { _keyholeRadius = keyholeRadius; }
// related to Octree Sending strategies // related to Octree Sending strategies
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; } int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
@ -88,6 +90,7 @@ protected:
float _cameraAspectRatio = 1.0f; float _cameraAspectRatio = 1.0f;
float _cameraNearClip = 0.0f; float _cameraNearClip = 0.0f;
float _cameraFarClip = 0.0f; float _cameraFarClip = 0.0f;
float _keyholeRadius { 0.0f };
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f); glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
// octree server sending items // octree server sending items

View file

@ -11,8 +11,55 @@
#include "CharacterController.h" #include "CharacterController.h"
#include <NumericalConstants.h>
#include "BulletUtil.h"
#include "PhysicsCollisionGroups.h" #include "PhysicsCollisionGroups.h"
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
const float JUMP_SPEED = 3.5f;
const float MAX_FALL_HEIGHT = 20.0f;
// helper class for simple ray-traces from character
class ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback {
public:
ClosestNotMe(btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
_me = me;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) {
if (rayResult.m_collisionObject == _me) {
return 1.0f;
}
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
protected:
btRigidBody* _me;
};
CharacterController::CharacterController() {
_halfHeight = 1.0f;
_enabled = false;
_floorDistance = MAX_FALL_HEIGHT;
_walkVelocity.setValue(0.0f, 0.0f, 0.0f);
_followVelocity.setValue(0.0f, 0.0f, 0.0f);
_jumpSpeed = JUMP_SPEED;
_isOnGround = false;
_isJumping = false;
_isFalling = false;
_isHovering = true;
_isPushingUp = false;
_jumpToHoverStart = 0;
_followTime = 0.0f;
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
}
bool CharacterController::needsRemoval() const { bool CharacterController::needsRemoval() const {
return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION); return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
} }
@ -23,7 +70,7 @@ bool CharacterController::needsAddition() const {
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
if (_dynamicsWorld != world) { if (_dynamicsWorld != world) {
if (_dynamicsWorld) { if (_dynamicsWorld) {
if (_rigidBody) { if (_rigidBody) {
_dynamicsWorld->removeRigidBody(_rigidBody); _dynamicsWorld->removeRigidBody(_rigidBody);
_dynamicsWorld->removeAction(this); _dynamicsWorld->removeAction(this);
@ -33,9 +80,13 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
if (world && _rigidBody) { if (world && _rigidBody) {
_dynamicsWorld = world; _dynamicsWorld = world;
_pendingFlags &= ~PENDING_FLAG_JUMP; _pendingFlags &= ~PENDING_FLAG_JUMP;
// Before adding the RigidBody to the world we must save its oldGravity to the side
// because adding an object to the world will overwrite it with the default gravity.
btVector3 oldGravity = _rigidBody->getGravity();
_dynamicsWorld->addRigidBody(_rigidBody, COLLISION_GROUP_MY_AVATAR, COLLISION_MASK_MY_AVATAR); _dynamicsWorld->addRigidBody(_rigidBody, COLLISION_GROUP_MY_AVATAR, COLLISION_MASK_MY_AVATAR);
_dynamicsWorld->addAction(this); _dynamicsWorld->addAction(this);
//reset(_dynamicsWorld); // restore gravity settings
_rigidBody->setGravity(oldGravity);
} }
} }
if (_dynamicsWorld) { if (_dynamicsWorld) {
@ -48,5 +99,283 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
} else { } else {
_pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION; _pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION;
} }
} }
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
// trace a ray straight down to see if we're standing on the ground
const btTransform& xform = _rigidBody->getWorldTransform();
// rayStart is at center of bottom sphere
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
// rayEnd is some short distance outside bottom sphere
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
btScalar rayLength = _radius + FLOOR_PROXIMITY_THRESHOLD;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
// scan down for nearby floor
ClosestNotMe rayCallback(_rigidBody);
rayCallback.m_closestHitFraction = 1.0f;
collisionWorld->rayTest(rayStart, rayEnd, rayCallback);
if (rayCallback.hasHit()) {
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
}
}
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
btVector3 actualVelocity = _rigidBody->getLinearVelocity();
btScalar actualSpeed = actualVelocity.length();
btVector3 desiredVelocity = _walkVelocity;
btScalar desiredSpeed = desiredVelocity.length();
const btScalar MIN_UP_PUSH = 0.1f;
if (desiredVelocity.dot(_currentUp) < MIN_UP_PUSH) {
_isPushingUp = false;
}
const btScalar MIN_SPEED = 0.001f;
if (_isHovering) {
if (desiredSpeed < MIN_SPEED) {
if (actualSpeed < MIN_SPEED) {
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
} else {
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
btScalar tau = glm::max(dt / HOVER_BRAKING_TIMESCALE, 1.0f);
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
}
} else {
const btScalar HOVER_ACCELERATION_TIMESCALE = 0.1f;
btScalar tau = dt / HOVER_ACCELERATION_TIMESCALE;
_rigidBody->setLinearVelocity(actualVelocity - tau * (actualVelocity - desiredVelocity));
}
} else {
if (onGround()) {
// walking on ground
if (desiredSpeed < MIN_SPEED) {
if (actualSpeed < MIN_SPEED) {
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
} else {
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
btScalar tau = dt / HOVER_BRAKING_TIMESCALE;
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
}
} else {
// TODO: modify desiredVelocity using floor normal
const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f;
btScalar tau = dt / WALK_ACCELERATION_TIMESCALE;
btVector3 velocityCorrection = tau * (desiredVelocity - actualVelocity);
// subtract vertical component
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
_rigidBody->setLinearVelocity(actualVelocity + velocityCorrection);
}
} else {
// transitioning to flying
btVector3 velocityCorrection = desiredVelocity - actualVelocity;
const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f;
btScalar tau = dt / FLY_ACCELERATION_TIMESCALE;
if (!_isPushingUp) {
// actually falling --> compute a different velocity attenuation factor
const btScalar FALL_ACCELERATION_TIMESCALE = 2.0f;
tau = dt / FALL_ACCELERATION_TIMESCALE;
// zero vertical component
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
}
_rigidBody->setLinearVelocity(actualVelocity + tau * velocityCorrection);
}
}
// Rather than add _followVelocity to the velocity of the RigidBody, we explicitly teleport
// the RigidBody forward according to the formula: distance = rate * time
if (_followVelocity.length2() > 0.0f) {
btTransform bodyTransform = _rigidBody->getWorldTransform();
bodyTransform.setOrigin(bodyTransform.getOrigin() + dt * _followVelocity);
_rigidBody->setWorldTransform(bodyTransform);
}
_followTime += dt;
}
void CharacterController::jump() {
// check for case where user is holding down "jump" key...
// we'll eventually tansition to "hover"
if (!_isJumping) {
if (!_isHovering) {
_jumpToHoverStart = usecTimestampNow();
_pendingFlags |= PENDING_FLAG_JUMP;
}
} else {
quint64 now = usecTimestampNow();
const quint64 JUMP_TO_HOVER_PERIOD = 75 * (USECS_PER_SECOND / 100);
if (now - _jumpToHoverStart > JUMP_TO_HOVER_PERIOD) {
_isPushingUp = true;
setHovering(true);
}
}
}
bool CharacterController::onGround() const {
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
return _floorDistance < FLOOR_PROXIMITY_THRESHOLD;
}
void CharacterController::setHovering(bool hover) {
if (hover != _isHovering) {
_isHovering = hover;
_isJumping = false;
if (_rigidBody) {
if (hover) {
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
} else {
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
}
}
}
}
void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
_boxScale = scale;
float x = _boxScale.x;
float z = _boxScale.z;
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
float halfHeight = 0.5f * _boxScale.y - radius;
float MIN_HALF_HEIGHT = 0.1f;
if (halfHeight < MIN_HALF_HEIGHT) {
halfHeight = MIN_HALF_HEIGHT;
}
// compare dimensions
float radiusDelta = glm::abs(radius - _radius);
float heightDelta = glm::abs(halfHeight - _halfHeight);
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
// shape hasn't changed --> nothing to do
} else {
if (_dynamicsWorld) {
// must REMOVE from world prior to shape update
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
}
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
// only need to ADD back when we happen to be enabled
if (_enabled) {
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
}
}
// it's ok to change offset immediately -- there are no thread safety issues here
_shapeLocalOffset = corner + 0.5f * _boxScale;
}
void CharacterController::setEnabled(bool enabled) {
if (enabled != _enabled) {
if (enabled) {
// Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit.
// Setting the ADD bit here works for all cases so we don't even bother checking other bits.
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
} else {
if (_dynamicsWorld) {
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
}
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
_isOnGround = false;
}
setHovering(true);
_enabled = enabled;
}
}
void CharacterController::updateUpAxis(const glm::quat& rotation) {
btVector3 oldUp = _currentUp;
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
if (!_isHovering) {
const btScalar MIN_UP_ERROR = 0.01f;
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
}
}
}
void CharacterController::setPositionAndOrientation(
const glm::vec3& position,
const glm::quat& orientation) {
// TODO: update gravity if up has changed
updateUpAxis(orientation);
btQuaternion bodyOrientation = glmToBullet(orientation);
btVector3 bodyPosition = glmToBullet(position + orientation * _shapeLocalOffset);
_characterBodyTransform = btTransform(bodyOrientation, bodyPosition);
}
void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
if (_enabled && _rigidBody) {
const btTransform& avatarTransform = _rigidBody->getWorldTransform();
rotation = bulletToGLM(avatarTransform.getRotation());
position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset;
}
}
void CharacterController::setTargetVelocity(const glm::vec3& velocity) {
//_walkVelocity = glmToBullet(_avatarData->getTargetVelocity());
_walkVelocity = glmToBullet(velocity);
}
void CharacterController::setFollowVelocity(const glm::vec3& velocity) {
_followVelocity = glmToBullet(velocity);
}
glm::vec3 CharacterController::getLinearVelocity() const {
glm::vec3 velocity(0.0f);
if (_rigidBody) {
velocity = bulletToGLM(_rigidBody->getLinearVelocity());
}
return velocity;
}
void CharacterController::preSimulation() {
if (_enabled && _dynamicsWorld) {
// slam body to where it is supposed to be
_rigidBody->setWorldTransform(_characterBodyTransform);
// scan for distant floor
// rayStart is at center of bottom sphere
btVector3 rayStart = _characterBodyTransform.getOrigin() - _halfHeight * _currentUp;
// rayEnd is straight down MAX_FALL_HEIGHT
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
ClosestNotMe rayCallback(_rigidBody);
rayCallback.m_closestHitFraction = 1.0f;
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
if (rayCallback.hasHit()) {
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
const btScalar MIN_HOVER_HEIGHT = 3.0f;
if (_isHovering && _floorDistance < MIN_HOVER_HEIGHT && !_isPushingUp) {
setHovering(false);
}
// TODO: use collision events rather than ray-trace test to disable jumping
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
if (_floorDistance < JUMP_PROXIMITY_THRESHOLD) {
_isJumping = false;
}
} else {
_floorDistance = FLT_MAX;
setHovering(true);
}
if (_pendingFlags & PENDING_FLAG_JUMP) {
_pendingFlags &= ~ PENDING_FLAG_JUMP;
if (onGround()) {
_isJumping = true;
btVector3 velocity = _rigidBody->getLinearVelocity();
velocity += _jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
}
}
_followTime = 0.0f;
}
void CharacterController::postSimulation() {
// postSimulation() exists for symmetry and just in case we need to do something here later
}

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