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

This commit is contained in:
Zach Fox 2017-10-18 10:37:47 -07:00
commit 3f0b08aea8
64 changed files with 864 additions and 280 deletions

39
.clang-format Normal file
View file

@ -0,0 +1,39 @@
Language: Cpp
Standard: Cpp11
BasedOnStyle: "Chromium"
ColumnLimit: 128
IndentWidth: 4
UseTab: Never
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: true
AfterClass: false
AfterControlStatement: false
AfterFunction: false
AfterNamespace: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
SplitEmptyFunction: false
SplitEmptyNamespace: true
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: InlineOnly
BreakConstructorInitializers: BeforeColon
BreakConstructorInitializersBeforeComma: true
IndentCaseLabels: true
ReflowComments: false
Cpp11BracedListStyle: false
ContinuationIndentWidth: 4
ConstructorInitializerAllOnOneLineOrOnePerLine: false
CompactNamespaces: true
SortIncludes: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyBreakBeforeFirstCallParameter: 1000

View file

@ -235,7 +235,8 @@ void updateConsumedCores() {
AssetServer::AssetServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_transferTaskPool(this),
_bakingTaskPool(this)
_bakingTaskPool(this),
_filesizeLimit(MAX_UPLOAD_SIZE)
{
// store the current state of image compression so we can reset it when this assignment is complete
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
@ -343,8 +344,8 @@ void AssetServer::completeSetup() {
auto maxBandwidthValue = assetServerObject[MAX_BANDWIDTH_OPTION];
auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1);
const int BITS_PER_MEGABITS = 1000 * 1000;
if (maxBandwidthFloat > 0.0) {
const int BITS_PER_MEGABITS = 1000 * 1000;
int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS;
nodeList->setConnectionMaxBandwidth(maxBandwidth);
qCInfo(asset_server) << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s."
@ -406,6 +407,15 @@ void AssetServer::completeSetup() {
qCCritical(asset_server) << "Asset Server assignment will not continue because mapping file could not be loaded.";
setFinished(true);
}
// get file size limit for an asset
static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit";
auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION];
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE);
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
}
}
void AssetServer::cleanupUnmappedFiles() {
@ -730,7 +740,7 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
if (senderNode->getCanWriteToAssetServer()) {
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
_transferTaskPool.start(task);
} else {
// this is a node the domain told us is not allowed to rez entities

View file

@ -127,6 +127,8 @@ private:
bool _wasGrayscaleTextureCompressionEnabled { false };
bool _wasNormalTextureCompressionEnabled { false };
bool _wasCubeTextureCompressionEnabled { false };
uint64_t _filesizeLimit;
};
#endif

View file

@ -22,10 +22,11 @@
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
const QDir& resourcesDir) :
const QDir& resourcesDir, uint64_t filesizeLimit) :
_receivedMessage(receivedMessage),
_senderNode(senderNode),
_resourcesDir(resourcesDir)
_resourcesDir(resourcesDir),
_filesizeLimit(filesizeLimit)
{
}
@ -48,7 +49,7 @@ void UploadAssetTask::run() {
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true);
replyPacket->writePrimitive(messageID);
if (fileSize > MAX_UPLOAD_SIZE) {
if (fileSize > _filesizeLimit) {
replyPacket->writePrimitive(AssetServerError::AssetTooLarge);
} else {
QByteArray fileData = buffer.read(fileSize);

View file

@ -26,7 +26,8 @@ class Node;
class UploadAssetTask : public QRunnable {
public:
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode,
const QDir& resourcesDir, uint64_t filesizeLimit);
void run() override;
@ -34,6 +35,7 @@ private:
QSharedPointer<ReceivedMessage> _receivedMessage;
QSharedPointer<Node> _senderNode;
QDir _resourcesDir;
uint64_t _filesizeLimit;
};
#endif // hifi_UploadAssetTask_h

View file

@ -118,6 +118,7 @@ macro(AUTOSCRIBE_SHADER_LIB)
foreach(SHADER_FILE ${SHADER_SOURCE_FILES})
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
set_property(SOURCE ${AUTOSCRIBE_GENERATED_FILE} PROPERTY SKIP_AUTOMOC ON)
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
endforeach()
#message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC})

View file

@ -7,10 +7,12 @@
#
function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
else()
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
if (NOT DEFINED ${_RESULT_NAME})
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
else()
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
endif()
endif()
endfunction()

View file

@ -956,6 +956,14 @@
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
"default": "",
"advanced": true
},
{
"name": "assets_filesize_limit",
"type": "int",
"label": "File Size Limit",
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
"default": 0,
"advanced": true
}
]
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 547 KiB

View file

@ -18,7 +18,7 @@
function shouldRaiseKeyboard() {
var nodeName = document.activeElement.nodeName;
var nodeType = document.activeElement.type;
if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url"].indexOf(nodeType) !== -1
if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url", "search"].indexOf(nodeType) !== -1
|| document.activeElement.nodeName === "TEXTAREA") {
return true;
} else {

View file

@ -2,14 +2,14 @@ name = mannequin
type = body+head
scale = 1
filename = mannequin/mannequin.baked.fbx
joint = jointEyeLeft = LeftEye
joint = jointRightHand = RightHand
joint = jointHead = Head
joint = jointEyeRight = RightEye
joint = jointLean = Spine
joint = jointNeck = Neck
joint = jointLeftHand = LeftHand
joint = jointRoot = Hips
joint = jointLean = Spine
joint = jointLeftHand = LeftHand
joint = jointHead = Head
joint = jointEyeLeft = LeftEye
joint = jointEyeRight = RightEye
joint = jointRightHand = RightHand
joint = jointNeck = Neck
freeJoint = LeftArm
freeJoint = LeftForeArm
freeJoint = RightArm
@ -18,72 +18,72 @@ bs = EyeBlink_L = blink = 1
bs = JawOpen = mouth_Open = 1
bs = LipsFunnel = Oo = 1
bs = BrowsU_L = brow_Up = 1
jointIndex = RightHandIndex2 = 27
jointIndex = LeftHandIndex2 = 51
jointIndex = RightUpLeg = 6
jointIndex = RightToe_End = 10
jointIndex = RightEye = 65
jointIndex = LeftHandPinky1 = 42
jointIndex = RightHandRing1 = 22
jointIndex = face = 67
jointIndex = LeftUpLeg = 1
jointIndex = LeftHand = 41
jointIndex = LeftHandMiddle1 = 58
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftEye = 64
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky4 = 45
jointIndex = RightArm = 15
jointIndex = LeftShoulder = 38
jointIndex = RightHandPinky2 = 19
jointIndex = RightHandThumb1 = 30
jointIndex = RightForeArm = 16
jointIndex = LeftHandMiddle3 = 60
jointIndex = Neck = 62
jointIndex = LeftHandThumb1 = 54
jointIndex = RightHandMiddle2 = 35
jointIndex = LeftHandMiddle4 = 61
jointIndex = mannequin = 68
jointIndex = Spine1 = 12
jointIndex = LeftHand = 41
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandMiddle3 = 36
jointIndex = LeftHandThumb4 = 57
jointIndex = RightToe_End = 10
jointIndex = LeftHandRing1 = 46
jointIndex = LeftForeArm = 40
jointIndex = RightHandIndex4 = 29
jointIndex = LeftShoulder = 38
jointIndex = RightHandMiddle4 = 37
jointIndex = RightShoulder = 14
jointIndex = LeftLeg = 2
jointIndex = LeftToe_End = 5
jointIndex = Hips = 0
jointIndex = RightFoot = 8
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandMiddle3 = 60
jointIndex = RightHandThumb1 = 30
jointIndex = Neck = 62
jointIndex = Spine = 11
jointIndex = RightHandThumb4 = 33
jointIndex = RightHandMiddle1 = 34
jointIndex = LeftHandIndex4 = 53
jointIndex = face = 68
jointIndex = RightHandRing3 = 24
jointIndex = LeftHandPinky4 = 45
jointIndex = LeftHandMiddle2 = 59
jointIndex = RightHandThumb3 = 32
jointIndex = LeftHandPinky3 = 44
jointIndex = HeadTop_End = 66
jointIndex = Spine1 = 12
jointIndex = LeftHandRing3 = 48
jointIndex = mannequin1 = 67
jointIndex = RightEye = 65
jointIndex = RightHandRing4 = 25
jointIndex = RightHandPinky4 = 21
jointIndex = LeftHandRing2 = 47
jointIndex = RightHandIndex3 = 28
jointIndex = RightUpLeg = 6
jointIndex = LeftArm = 39
jointIndex = LeftHandThumb3 = 56
jointIndex = RightHandIndex2 = 27
jointIndex = RightForeArm = 16
jointIndex = RightArm = 15
jointIndex = RightHandRing2 = 23
jointIndex = LeftHandMiddle1 = 58
jointIndex = Spine2 = 13
jointIndex = LeftHandThumb2 = 55
jointIndex = RightHandMiddle2 = 35
jointIndex = RightHandPinky1 = 18
jointIndex = LeftUpLeg = 1
jointIndex = RightLeg = 7
jointIndex = LeftHandIndex2 = 51
jointIndex = RightHand = 17
jointIndex = LeftHandIndex3 = 52
jointIndex = RightHandIndex3 = 28
jointIndex = RightHandMiddle4 = 37
jointIndex = LeftLeg = 2
jointIndex = RightHandMiddle1 = 34
jointIndex = Spine2 = 13
jointIndex = LeftHandMiddle2 = 59
jointIndex = LeftHandPinky3 = 44
jointIndex = LeftHandThumb3 = 56
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandRing3 = 48
jointIndex = HeadTop_End = 66
jointIndex = LeftHandThumb4 = 57
jointIndex = RightHandThumb3 = 32
jointIndex = RightHandPinky1 = 18
jointIndex = RightLeg = 7
jointIndex = RightHandMiddle3 = 36
jointIndex = RightHandPinky3 = 20
jointIndex = LeftToeBase = 4
jointIndex = LeftForeArm = 40
jointIndex = RightShoulder = 14
jointIndex = LeftHandRing2 = 47
jointIndex = LeftHandThumb2 = 55
jointIndex = Head = 63
jointIndex = RightHandRing4 = 25
jointIndex = LeftHandRing1 = 46
jointIndex = LeftFoot = 3
jointIndex = RightHandRing3 = 24
jointIndex = RightHandThumb4 = 33
jointIndex = LeftArm = 39
jointIndex = LeftToe_End = 5
jointIndex = RightHandPinky3 = 20
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky1 = 42
jointIndex = RightToeBase = 9
jointIndex = RightHandPinky4 = 21
jointIndex = Spine = 11
jointIndex = LeftHandIndex4 = 53
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftToeBase = 4
jointIndex = LeftHandPinky2 = 43
jointIndex = RightHandIndex4 = 29
jointIndex = Hips = 0
jointIndex = RightHandRing2 = 23
jointIndex = RightHandRing1 = 22
jointIndex = LeftHandThumb1 = 54
jointIndex = LeftEye = 64
jointIndex = Head = 63

BIN
interface/resources/meshes/mannequin/Eyes.ktx Executable file → Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
interface/resources/meshes/mannequin/mannequin.baked.fbx Executable file → Normal file

Binary file not shown.

View file

@ -17,6 +17,7 @@ import "./hifi/audio" as HifiAudio
Hifi.AvatarInputs {
id: root;
objectName: "AvatarInputs"
property int modality: Qt.NonModal
width: audio.width;
height: audio.height;
x: 10; y: 5;

View file

@ -8,6 +8,9 @@ Item {
anchors.leftMargin: 300
objectName: "StatsItem"
property int modality: Qt.NonModal
implicitHeight: row.height
implicitWidth: row.width
Component.onCompleted: {
stats.parentChanged.connect(fill);
@ -18,8 +21,9 @@ Item {
}
function fill() {
// Explicitly fill in order to avoid warnings at shutdown
anchors.fill = parent;
// This will cause a warning at shutdown, need to find another way to remove
// the warning other than filling the anchors to the parent
anchors.horizontalCenter = parent.horizontalCenter
}
Hifi.Stats {

View file

@ -13,6 +13,7 @@ import "."
Rectangle {
id: keyboardBase
objectName: "keyboard"
anchors.left: parent.left
anchors.right: parent.right
@ -27,6 +28,8 @@ Rectangle {
readonly property int mirrorTextHeight: keyboardRowHeight
property bool password: false
property alias mirroredText: mirrorText.text
property bool showMirrorText: true
readonly property int raisedHeight: 200
@ -112,16 +115,20 @@ Rectangle {
color: "#252525"
anchors.horizontalCenter: parent.horizontalCenter
TextEdit {
TextInput {
id: mirrorText
visible: showMirrorText
size: 13.5
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
font.family: ralewaySemiBold.name
font.pointSize: 13.5
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: "#FFFFFF";
anchors.fill: parent
wrapMode: Text.WordWrap
readOnly: false // we need to leave this property read-only to allow control to accept QKeyEvent
readOnly: false // we need this to allow control to accept QKeyEvent
selectByMouse: false
echoMode: password ? TextInput.Password : TextInput.Normal
Keys.onPressed: {
if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) {

View file

@ -301,15 +301,19 @@ FocusScope {
function isPointOnWindow(point) {
for (var i = 0; i < desktop.visibleChildren.length; i++) {
var child = desktop.visibleChildren[i];
if (child.visible) {
if (child.hasOwnProperty("modality")) {
var mappedPoint = child.mapFromGlobal(point.x, point.y);
if (child.hasOwnProperty("modality")) {
var mappedPoint = mapToItem(child, point.x, point.y);
if (child.hasOwnProperty("frame")) {
var outLine = child.frame.children[2];
var framePoint = outLine.mapFromGlobal(point.x, point.y);
if (child.contains(mappedPoint) || outLine.contains(framePoint)) {
if (outLine.contains(framePoint)) {
return true;
}
}
if (child.contains(mappedPoint)) {
return true;
}
}
}
return false;

View file

@ -207,6 +207,17 @@
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
#ifdef DEBUG_EVENT_QUEUE
// This is a HACK that uses private headers included with the qt source distrubution.
// To use this feature you need to add these directores to your include path:
// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1/QtCore
// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1
#define QT_BOOTSTRAPPED
#include <private/qthread_p.h>
#include <private/qobject_p.h>
#undef QT_BOOTSTRAPPED
#endif
extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}
@ -264,9 +275,7 @@ private:
switch ((int)event->type()) {
case ApplicationEvent::Render:
render();
// Ensure we never back up the render events. Each render should be triggered only in response
// to the NEXT render event after the last render occured
QCoreApplication::removePostedEvents(this, ApplicationEvent::Render);
qApp->_pendingRenderEvent.store(false);
return true;
default:
@ -2713,9 +2722,14 @@ bool Application::importFromZIP(const QString& filePath) {
return true;
}
// thread-safe
void Application::onPresent(quint32 frameCount) {
postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority);
if (_renderEventHandler && !isAboutToQuit()) {
bool expected = false;
if (_pendingIdleEvent.compare_exchange_strong(expected, true)) {
postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority);
}
expected = false;
if (_renderEventHandler && !isAboutToQuit() && _pendingRenderEvent.compare_exchange_strong(expected, true)) {
postEvent(_renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render));
}
}
@ -2782,7 +2796,26 @@ bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) {
return false;
}
#ifdef DEBUG_EVENT_QUEUE
static int getEventQueueSize(QThread* thread) {
auto threadData = QThreadData::get2(thread);
QMutexLocker locker(&threadData->postEventList.mutex);
return threadData->postEventList.size();
}
static void dumpEventQueue(QThread* thread) {
auto threadData = QThreadData::get2(thread);
QMutexLocker locker(&threadData->postEventList.mutex);
qDebug() << "AJT: event list, size =" << threadData->postEventList.size();
for (auto& postEvent : threadData->postEventList) {
QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None);
qDebug() << "AJT: " << type;
}
}
#endif // DEBUG_EVENT_QUEUE
bool Application::event(QEvent* event) {
if (!Menu::getInstance()) {
return false;
}
@ -2802,8 +2835,18 @@ bool Application::event(QEvent* event) {
// see (windowMinimizedChanged)
case ApplicationEvent::Idle:
idle();
// Don't process extra idle events that arrived in the event queue while we were doing this idle
QCoreApplication::removePostedEvents(this, ApplicationEvent::Idle);
#ifdef DEBUG_EVENT_QUEUE
{
int count = getEventQueueSize(QThread::currentThread());
if (count > 400) {
dumpEventQueue(QThread::currentThread());
}
}
#endif // DEBUG_EVENT_QUEUE
_pendingIdleEvent.store(false);
return true;
case QEvent::MouseMove:
@ -7209,7 +7252,7 @@ void Application::updateDisplayMode() {
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
auto desktop = offscreenUi->getDesktop();
if (desktop) {
desktop->setProperty("repositionLocked", wasRepositionLocked);

View file

@ -718,5 +718,8 @@ private:
LaserPointerManager _laserPointerManager;
friend class RenderEventHandler;
std::atomic<bool> _pendingIdleEvent { false };
std::atomic<bool> _pendingRenderEvent { false };
};
#endif // hifi_Application_h

View file

@ -72,6 +72,12 @@ void Application::paintGL() {
{
QMutexLocker viewLocker(&_renderArgsMutex);
renderArgs = _appRenderArgs._renderArgs;
// don't render if there is no context.
if (!_appRenderArgs._renderArgs._context) {
return;
}
HMDSensorPose = _appRenderArgs._headPose;
eyeToWorld = _appRenderArgs._eyeToWorld;
sensorToWorld = _appRenderArgs._sensorToWorld;

View file

@ -15,13 +15,14 @@
#include "avatar/AvatarManager.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) :
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled) :
_renderingEnabled(enabled),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_centerEndY(centerEndY),
_lockEnd(lockEnd)
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd)
{
_rayPickUID = DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps);
@ -86,6 +87,10 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
updateRenderStateOverlay(_renderStates[state].getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps);
updateRenderStateOverlay(_renderStates[state].getEndID(), endProps);
QVariant endDim = endProps.toMap()["dimensions"];
if (endDim.isValid()) {
_renderStates[state].setEndDim(vec3FromVariant(endDim));
}
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
@ -154,10 +159,14 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
if (_distanceScaleEnd) {
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec) * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
endProps.insert("dimensions", vec3toVariant(dim));
}
if (_centerEndY) {
endProps.insert("position", end);
} else {
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP;
endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y)));
}
@ -264,6 +273,7 @@ RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, cons
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
}
if (!_endID.isNull()) {
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}

View file

@ -32,6 +32,9 @@ public:
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
const glm::vec3& getEndDim() const { return _endDim; }
void deleteOverlays();
private:
@ -41,6 +44,8 @@ private:
bool _startIgnoreRays;
bool _pathIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _endDim;
};
@ -52,7 +57,7 @@ public:
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled);
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled);
~LaserPointer();
QUuid getRayUID() { return _rayPickUID; }
@ -88,6 +93,7 @@ private:
bool _faceAvatar;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
std::pair<QUuid, bool> _objectLockEnd { std::pair<QUuid, bool>(QUuid(), false)};
QUuid _rayPickUID;

View file

@ -11,8 +11,8 @@
#include "LaserPointerManager.h"
QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) {
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled);
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled) {
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled);
if (!laserPointer->getRayUID().isNull()) {
QWriteLocker containsLock(&_containsLock);
QUuid id = QUuid::createUuid();

View file

@ -22,7 +22,7 @@ class LaserPointerManager {
public:
QUuid createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled);
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool enabled);
void removeLaserPointer(const QUuid uid);
void enableLaserPointer(const QUuid uid);
void disableLaserPointer(const QUuid uid);

View file

@ -32,6 +32,11 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert
lockEnd = propertyMap["lockEnd"].toBool();
}
bool distanceScaleEnd = false;
if (propertyMap["distanceScaleEnd"].isValid()) {
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
@ -66,7 +71,7 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert
}
}
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled);
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, enabled);
}
void LaserPointerScriptingInterface::editRenderState(QUuid uid, const QString& renderState, const QVariant& properties) {

View file

@ -32,6 +32,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHightlightID WRITE setCurrentHomeButtonHightlightID)
Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
public:
@ -97,6 +98,9 @@ public:
void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; }
QUuid getCurrentHomeButtonID() const { return _homeButtonID; }
void setCurrentHomeButtonHightlightID(QUuid homeButtonHightlightID) { _homeButtonHightlightID = homeButtonHightlightID; }
QUuid getCurrentHomeButtonHightlightID() const { return _homeButtonHightlightID; }
void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; }
QUuid getCurrentTabletScreenID() const { return _tabletScreenID; }
@ -105,6 +109,7 @@ private:
QUuid _tabletUIID; // this is the entityID of the tablet frame
QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui.
QUuid _homeButtonID;
QUuid _homeButtonHightlightID;
QUuid _tabletEntityID;
// Get the position of the HMD

View file

@ -106,14 +106,15 @@ extern std::atomic<size_t> DECIMATED_TEXTURE_COUNT;
extern std::atomic<size_t> RECTIFIED_TEXTURE_COUNT;
void Stats::updateStats(bool force) {
QQuickItem* parent = parentItem();
if (!force) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
if (isVisible()) {
setVisible(false);
if (parent->isVisible()) {
parent->setVisible(false);
}
return;
} else if (!isVisible()) {
setVisible(true);
} else if (!parent->isVisible()) {
parent->setVisible(true);
}
}

View file

@ -135,6 +135,7 @@ static const int32_t exp2Table[1 << EXP2_TABBITS][3] = {
static const int IEEE754_FABS_MASK = 0x7fffffff;
static const int IEEE754_MANT_BITS = 23;
static const int IEEE754_EXPN_BITS = 8;
static const int IEEE754_EXPN_BIAS = 127;
//
@ -152,7 +153,7 @@ static inline int32_t peaklog2(float* input) {
// split into e and x - 1.0
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff;
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
// saturate
if (e > 31) {
@ -191,7 +192,7 @@ static inline int32_t peaklog2(float* input0, float* input1) {
// split into e and x - 1.0
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff;
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
// saturate
if (e > 31) {
@ -234,7 +235,7 @@ static inline int32_t peaklog2(float* input0, float* input1, float* input2, floa
// split into e and x - 1.0
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
int32_t x = (peak << (31 - IEEE754_MANT_BITS)) & 0x7fffffff;
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
// saturate
if (e > 31) {
@ -259,30 +260,30 @@ static inline int32_t peaklog2(float* input0, float* input1, float* input2, floa
// Count Leading Zeros
// Emulates the CLZ (ARM) and LZCNT (x86) instruction
//
static inline int CLZ(uint32_t x) {
static inline int CLZ(uint32_t u) {
if (x == 0) {
if (u == 0) {
return 32;
}
int e = 0;
if (x < 0x00010000) {
x <<= 16;
if (u < 0x00010000) {
u <<= 16;
e += 16;
}
if (x < 0x01000000) {
x <<= 8;
if (u < 0x01000000) {
u <<= 8;
e += 8;
}
if (x < 0x10000000) {
x <<= 4;
if (u < 0x10000000) {
u <<= 4;
e += 4;
}
if (x < 0x40000000) {
x <<= 2;
if (u < 0x40000000) {
u <<= 2;
e += 2;
}
if (x < 0x80000000) {
if (u < 0x80000000) {
e += 1;
}
return e;
@ -290,19 +291,19 @@ static inline int CLZ(uint32_t x) {
//
// Compute -log2(x) for x=[0,1] in Q31, result in Q26
// x = 0 returns 0x7fffffff
// x < 0 undefined
// x <= 0 returns 0x7fffffff
//
static inline int32_t fixlog2(int32_t x) {
if (x == 0) {
if (x <= 0) {
return 0x7fffffff;
}
// split into e and x - 1.0
int e = CLZ((uint32_t)x);
x <<= e; // normalize to [0x80000000, 0xffffffff]
x &= 0x7fffffff; // x - 1.0
uint32_t u = (uint32_t)x;
int e = CLZ(u);
u <<= e; // normalize to [0x80000000, 0xffffffff]
x = u & 0x7fffffff; // x - 1.0
int k = x >> (31 - LOG2_TABBITS);
@ -320,13 +321,18 @@ static inline int32_t fixlog2(int32_t x) {
//
// Compute exp2(-x) for x=[0,32] in Q26, result in Q31
// x < 0 undefined
// x <= 0 returns 0x7fffffff
//
static inline int32_t fixexp2(int32_t x) {
if (x <= 0) {
return 0x7fffffff;
}
// split into e and 1.0 - x
int e = x >> LOG2_FRACBITS;
x = ~(x << LOG2_INTBITS) & 0x7fffffff;
uint32_t u = (uint32_t)x;
int e = u >> LOG2_FRACBITS;
x = ~(u << LOG2_INTBITS) & 0x7fffffff;
int k = x >> (31 - EXP2_TABBITS);

View file

@ -40,7 +40,7 @@ class MonoDCBlock {
public:
void process(int32_t& x) {
x <<= 15; // scale to Q30
x *= (1 << 15); // scale to Q30
x -= _dcOffset; // remove DC
_dcOffset += x >> 13; // pole = (1.0 - 2^-13) = 0.9999
}
@ -53,8 +53,8 @@ class StereoDCBlock {
public:
void process(int32_t& x0, int32_t& x1) {
x0 <<= 15;
x1 <<= 15;
x0 *= (1 << 15);
x1 *= (1 << 15);
x0 -= _dcOffset[0];
x1 -= _dcOffset[1];
@ -71,10 +71,10 @@ class QuadDCBlock {
public:
void process(int32_t& x0, int32_t& x1, int32_t& x2, int32_t& x3) {
x0 <<= 15;
x1 <<= 15;
x2 <<= 15;
x3 <<= 15;
x0 *= (1 << 15);
x1 *= (1 << 15);
x2 *= (1 << 15);
x3 *= (1 << 15);
x0 -= _dcOffset[0];
x1 -= _dcOffset[1];
@ -100,10 +100,10 @@ protected:
int _histogram[NHIST] = {};
// peakhold
int32_t _holdMin = 0x7fffffff;
int32_t _holdInc = 0x7fffffff;
uint32_t _holdMin = 0x7fffffff;
uint32_t _holdInc = 0x7fffffff;
uint32_t _holdMax = 0x7fffffff;
int32_t _holdRel = 0x7fffffff;
uint32_t _holdRel = 0x7fffffff;
int32_t _holdPeak = 0x7fffffff;
// hysteresis
@ -177,18 +177,23 @@ void GateImpl::setThreshold(float threshold) {
void GateImpl::setHold(float hold) {
const double RELEASE = 100.0; // release = 100ms
const double PROGHOLD = 0.100; // progressive hold = 100ms
const double PROGHOLD = 100.0; // progressive hold = 100ms
// pure hold = 1 to 1000ms
hold = MAX(hold, 1.0f);
hold = MIN(hold, 1000.0f);
// compute final tc
_holdMin = msToTc(RELEASE, _sampleRate);
_holdInc = (int32_t)((_holdMin - 0x7fffffff) / (PROGHOLD * _sampleRate));
_holdInc = MIN(_holdInc, -1); // prevent 0 on long releases
_holdMax = 0x7fffffff - (uint32_t)(_holdInc * (double)hold/1000.0 * _sampleRate);
// compute tc increment, to progress from 0x7fffffff to _holdMin in PROGHOLD ms
double progSamples = PROGHOLD/1000.0 * _sampleRate;
_holdInc = (uint32_t)((0x7fffffff - _holdMin) / progSamples);
_holdInc = MAX(_holdInc, 1); // prevent 0 on long releases
// compute initial tc, to progress from _holdMax to 0x7fffffff in hold ms
double holdSamples = (double)hold/1000.0 * _sampleRate;
_holdMax = 0x7fffffff + (uint32_t)(_holdInc * holdSamples);
}
//
@ -318,8 +323,6 @@ void GateImpl::processHistogram(int numFrames) {
// smooth threshold update
_threshAdapt = threshold + MULQ31((_threshAdapt - threshold), tcThreshold);
//printf("threshold = %0.1f\n", (_threshAdapt - (LOG2_HEADROOM_Q15 << LOG2_FRACBITS)) * -6.02f / (1 << LOG2_FRACBITS));
}
//
@ -336,10 +339,8 @@ int32_t GateImpl::peakhold(int32_t peak) {
// (_holdRel > _holdMin) progressive hold
// (_holdRel = _holdMin) release
_holdRel += _holdInc; // update progressive hold
_holdRel = MAX((uint32_t)_holdRel, (uint32_t)_holdMin); // saturate at final value
int32_t tc = MIN((uint32_t)_holdRel, 0x7fffffff);
_holdRel -= _holdInc; // update progressive hold
int32_t tc = MIN(MAX(_holdRel, _holdMin), 0x7fffffff); // saturate to [_holdMin, 0x7fffffff]
peak += MULQ31((_holdPeak - peak), tc); // apply release
} else {

View file

@ -46,6 +46,10 @@ static const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30;
// _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset.
static const quint64 FRAMES_AVAILABLE_STAT_WINDOW_USECS = 10 * USECS_PER_SECOND;
// When the audio codec is switched, temporary codec mismatch is expected due to packets in-flight.
// A SelectedAudioFormat packet is not sent until this threshold is exceeded.
static const int MAX_MISMATCHED_AUDIO_CODEC_COUNT = 10;
InboundAudioStream::InboundAudioStream(int numChannels, int numFrames, int numBlocks, int numStaticJitterBlocks) :
_ringBuffer(numChannels * numFrames, numBlocks),
_numChannels(numChannels),
@ -153,6 +157,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
// If we recieved a SilentAudioFrame from our sender, we might want to drop
// some of the samples in order to catch up to our desired jitter buffer size.
writeDroppableSilentFrames(networkFrames);
} else {
// note: PCM and no codec are identical
bool selectedPCM = _selectedCodecName == "pcm" || _selectedCodecName == "";
@ -160,20 +165,33 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
if (codecInPacket == _selectedCodecName || (packetPCM && selectedPCM)) {
auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead());
parseAudioData(message.getType(), afterProperties);
_mismatchedAudioCodecCount = 0;
} else {
qDebug(audio) << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket << "writing silence";
_mismatchedAudioCodecCount++;
qDebug(audio) << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket;
// Since the data in the stream is using a codec that we aren't prepared for,
// we need to let the codec know that we don't have data for it, this will
// allow the codec to interpolate missing data and produce a fade to silence.
lostAudioData(1);
// inform others of the mismatch
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
if (sendingNode) {
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
if (packetPCM) {
// If there are PCM packets in-flight after the codec is changed, use them.
auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead());
_ringBuffer.writeData(afterProperties.data(), afterProperties.size());
} else {
// Since the data in the stream is using a codec that we aren't prepared for,
// we need to let the codec know that we don't have data for it, this will
// allow the codec to interpolate missing data and produce a fade to silence.
lostAudioData(1);
}
if (_mismatchedAudioCodecCount > MAX_MISMATCHED_AUDIO_CODEC_COUNT) {
_mismatchedAudioCodecCount = 0;
// inform others of the mismatch
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
if (sendingNode) {
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
qDebug(audio) << "Codec mismatch threshold exceeded, SelectedAudioFormat(" << _selectedCodecName << " ) sent";
}
}
}
}
break;

View file

@ -186,6 +186,7 @@ protected:
CodecPluginPointer _codec;
QString _selectedCodecName;
Decoder* _decoder { nullptr };
int _mismatchedAudioCodecCount { 0 };
};
float calculateRepeatedFrameFadeFactor(int indexOfRepeat);

View file

@ -983,6 +983,9 @@ public:
glm::vec2 dstCoord;
glm::ivec2 srcPixel;
for (int y = 0; y < faceWidth; ++y) {
QRgb* destScanLineBegin = reinterpret_cast<QRgb*>( image.scanLine(y) );
QRgb* destPixelIterator = destScanLineBegin;
dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom
for (int x = 0; x < faceWidth; ++x) {
dstCoord.x = (x + 0.5f) * dstInvSize.x;
@ -995,13 +998,19 @@ public:
srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight);
if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) {
image.setPixel(x, y, source.pixel(QPoint(srcPixel.x, srcPixel.y)));
// We can't directly use the pixel() method because that launches a pixel color conversion to output
// a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which
// are not convertible by Qt. The same goes with the setPixel method, by the way.
const QRgb* sourcePixelIterator = reinterpret_cast<const QRgb*>(source.scanLine(srcPixel.y));
sourcePixelIterator += srcPixel.x;
*destPixelIterator = *sourcePixelIterator;
// Keep for debug, this is showing the dir as a color
// glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256);
// unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16);
// image.setPixel(x, y, val);
// *destPixelIterator = val;
}
++destPixelIterator;
}
}
return image;
@ -1192,6 +1201,10 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage&
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(image, HDR_FORMAT);
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
@ -1229,13 +1242,6 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage&
faces.push_back(faceImage);
}
}
if (image.format() != QIMAGE_HDR_FORMAT) {
for (auto& face : faces) {
face = convertToHDRFormat(face, HDR_FORMAT);
}
}
} else {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;

View file

@ -620,6 +620,12 @@ void NetworkTexture::ktxMipRequestFinished() {
texture->assignStoredMip(mipLevel, data.size(), reinterpret_cast<const uint8_t*>(data.data()));
// If mip level assigned above is still unavailable, then we assume future requests will also fail.
auto minMipLevel = texture->minAvailableMipLevel();
if (minMipLevel > mipLevel) {
return;
}
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),

View file

@ -37,7 +37,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentityLookAtSnapping);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::UpdatedMannequinDefaultAvatar);
case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
case PacketType::ICEServerHeartbeat:

View file

@ -301,6 +301,7 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AvatarIdentitySequenceFront,
IsReplicatedInAvatarIdentity,
AvatarIdentityLookAtSnapping,
UpdatedMannequinDefaultAvatar
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -41,6 +41,7 @@
#include <gl/OffscreenGLCanvas.h>
#include <gl/GLHelpers.h>
#include <gl/Context.h>
#include <shared/ReadWriteLockable.h>
#include "types/FileTypeProfile.h"
#include "types/HFWebEngineProfile.h"
@ -52,6 +53,53 @@ Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
class OffscreenQmlWhitelist : public Dependency, private ReadWriteLockable {
SINGLETON_DEPENDENCY
public:
void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback) {
withWriteLock([&] {
for (const auto& url : urls) {
_callbacks[url].push_back(callback);
}
});
}
QList<QmlContextCallback> getCallbacksForUrl(const QUrl& url) const {
return resultWithReadLock<QList<QmlContextCallback>>([&] {
QList<QmlContextCallback> result;
auto itr = _callbacks.find(url);
if (_callbacks.end() != itr) {
result = *itr;
}
return result;
});
}
private:
QHash<QUrl, QList<QmlContextCallback>> _callbacks;
};
QSharedPointer<OffscreenQmlWhitelist> getQmlWhitelist() {
static std::once_flag once;
std::call_once(once, [&] {
DependencyManager::set<OffscreenQmlWhitelist>();
});
return DependencyManager::get<OffscreenQmlWhitelist>();
}
void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback) {
getQmlWhitelist()->addWhitelistContextHandler(urls, callback);
}
QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {};
struct TextureSet {
// The number of surfaces with this size
size_t count { 0 };
@ -640,18 +688,26 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl);
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) {
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Called load on a non-surface thread";
}
// Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
createNewContext |= !callbacks.empty();
callbacks.push_back(onQmlLoadedCallback);
QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) {
targetContext = new QQmlContext(targetContext);
}
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
@ -659,29 +715,32 @@ void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this,
[this, qmlComponent, targetContext, onQmlLoadedCallback](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback);
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, callbacks);
});
return;
}
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback);
finishQmlLoad(qmlComponent, targetContext, callbacks);
}
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) {
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) {
void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
load(qmlSource, false, onQmlLoadedCallback);
}
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache();
}
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) {
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
@ -716,7 +775,9 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
// Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) {
onQmlLoadedCallback(qmlContext, newObject);
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
@ -751,8 +812,11 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work
onQmlLoadedCallback(qmlContext, newObject);
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
}
void OffscreenQmlSurface::updateQuick() {
@ -1018,6 +1082,32 @@ void OffscreenQmlSurface::synthesizeKeyPress(QString key, QObject* targetOverrid
}
}
static void forEachKeyboard(QQuickItem* item, std::function<void(QQuickItem*)> function) {
QObject* itemObject = item;
while (itemObject) {
if (itemObject->parent()) {
itemObject = itemObject->parent();
} else {
break;
}
}
auto keyboards = itemObject->findChildren<QObject*>("keyboard");
for (auto keyboardObject : keyboards) {
auto keyboard = qobject_cast<QQuickItem*>(keyboardObject);
if (keyboard == nullptr) {
continue;
}
if (function) {
function(keyboard);
}
}
}
static const int TEXTINPUT_PASSWORD = 2;
void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
#if Q_OS_ANDROID
return;
@ -1030,6 +1120,26 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n
// if HMD is being worn, allow keyboard to open. allow it to close, HMD or not.
if (!raised || qApp->property(hifi::properties::HMD).toBool()) {
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
if (!item) {
return;
}
auto echoMode = item->property("echoMode");
bool isPasswordField = echoMode.isValid() && echoMode.toInt() == TEXTINPUT_PASSWORD;
// we need to somehow pass 'isPasswordField' to visible keyboard so it will change its 'mirror text' to asterixes
// the issue in some cases there might be more than one keyboard in object tree and it is hard to understand which one is being used at the moment
// unfortunately attempts to check for visibility failed becuase visibility is not updated yet. So... I don't see other way than just update properties for all the keyboards
forEachKeyboard(item, [&](QQuickItem* keyboard) {
keyboard->setProperty("mirroredText", QVariant::fromValue(QString("")));
keyboard->setProperty("password", isPasswordField);
});
// for future probably makes sense to consider one of the following:
// 1. make keyboard a singleton, which will be dynamically re-parented before showing
// 2. track currently visible keyboard somewhere, allow to subscribe for this signal
// any of above should also eliminate need in duplicated properties and code below
while (item) {
// Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here.
numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox";

View file

@ -35,12 +35,18 @@ class QQuickItem;
// one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>;
class OffscreenQmlSurface : public QObject {
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public:
static void setSharedContext(QOpenGLContext* context);
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
OffscreenQmlSurface();
virtual ~OffscreenQmlSurface();
@ -50,12 +56,10 @@ public:
void resize(const QSize& size, bool forceResize = false);
QSize size() const;
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {});
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {});
Q_INVOKABLE void load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {});
Q_INVOKABLE void load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {}) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling
@ -120,7 +124,7 @@ protected:
private:
static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps);

View file

@ -53,4 +53,8 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() {
window.clearDebugWindow();
});
Script.scriptEnding.connect(function () {
window.close();
})
}());

View file

@ -546,6 +546,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args);
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release',
grabbedEntity: this.targetEntityID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
ensureDynamic(this.targetEntityID);
this.targetEntityID = null;
this.messageGrabEntity = false;

View file

@ -23,6 +23,8 @@ Script.include("/~/system/libraries/controllers.js");
(function() {
var PICK_WITH_HAND_RAY = true;
var SEARCH_SPHERE_SIZE = 0.0132;
var dim = {x: SEARCH_SPHERE_SIZE, y: SEARCH_SPHERE_SIZE, z: SEARCH_SPHERE_SIZE};
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -37,6 +39,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -58,6 +61,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -135,10 +139,6 @@ Script.include("/~/system/libraries/controllers.js");
100);
this.updateLaserPointer = function(controllerData) {
var SEARCH_SPHERE_SIZE = 0.011;
var MIN_SPHERE_SIZE = 0.0005;
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE) * MyAvatar.sensorToWorldScale;
var dim = {x: radius, y: radius, z: radius};
var mode = "hold";
if (!this.distanceHolding && !this.distanceRotating) {
if (controllerData.triggerClicks[this.hand]) {
@ -150,16 +150,10 @@ Script.include("/~/system/libraries/controllers.js");
var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer;
if (mode === "full") {
var fullEndToEdit = PICK_WITH_HAND_RAY ? this.fullEnd : fullEnd;
fullEndToEdit.dimensions = dim;
LaserPointers.editRenderState(laserPointerID, mode, { path: fullPath, end: fullEndToEdit });
this.contextOverlayTimer = false;
this.destroyContextOverlay();
} else if (mode === "half") {
var halfEndToEdit = PICK_WITH_HAND_RAY ? this.halfEnd : halfEnd;
halfEndToEdit.dimensions = dim;
LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit});
}
LaserPointers.enableLaserPointer(laserPointerID);
LaserPointers.setRenderState(laserPointerID, mode);
if (this.distanceHolding || this.distanceRotating) {
@ -577,8 +571,6 @@ Script.include("/~/system/libraries/controllers.js");
LaserPointers.removeLaserPointer(this.laserPointer);
};
this.halfEnd = halfEnd;
this.fullEnd = fullEnd;
this.laserPointer = LaserPointers.createLaserPointer({
joint: (this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
@ -586,6 +578,7 @@ Script.include("/~/system/libraries/controllers.js");
posOffset: getGrabPointSphereOffset(this.handToController(), true),
renderStates: renderStates,
faceAvatar: true,
distanceScaleEnd: true,
defaultRenderStates: defaultRenderStates
});
}

View file

@ -16,6 +16,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
var SEARCH_SPHERE_SIZE = 0.0132;
var dim = {x: SEARCH_SPHERE_SIZE, y: SEARCH_SPHERE_SIZE, z: SEARCH_SPHERE_SIZE};
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -30,6 +32,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -51,6 +54,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -107,10 +111,6 @@ Script.include("/~/system/libraries/controllers.js");
};
this.updateLaserPointer = function(controllerData) {
var SEARCH_SPHERE_SIZE = 0.011;
var MIN_SPHERE_SIZE = 0.0005;
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE);
var dim = {x: radius, y: radius, z: radius};
var mode = "none";
if (controllerData.triggerClicks[this.hand]) {
mode = "full";
@ -118,18 +118,8 @@ Script.include("/~/system/libraries/controllers.js");
mode = "half";
}
var laserPointerID = this.laserPointer;
if (mode === "full") {
var fullEndToEdit = this.fullEnd;
fullEndToEdit.dimensions = dim;
LaserPointers.editRenderState(laserPointerID, mode, {path: fullPath, end: fullEndToEdit});
} else if (mode === "half") {
var halfEndToEdit = this.halfEnd;
halfEndToEdit.dimensions = dim;
LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit});
}
LaserPointers.enableLaserPointer(laserPointerID);
LaserPointers.setRenderState(laserPointerID, mode);
LaserPointers.enableLaserPointer(this.laserPointer);
LaserPointers.setRenderState(this.laserPointer, mode);
};
this.laserPointerOff = function() {
@ -192,8 +182,6 @@ Script.include("/~/system/libraries/controllers.js");
return makeRunningValues(true, [this.targetEntityID], []);
};
this.halfEnd = halfEnd;
this.fullEnd = fullEnd;
this.laserPointer = LaserPointers.createLaserPointer({
joint: (this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
@ -201,6 +189,7 @@ Script.include("/~/system/libraries/controllers.js");
posOffset: getGrabPointSphereOffset(this.handToController(), true),
renderStates: renderStates,
faceAvatar: true,
distanceScaleEnd: true,
defaultRenderStates: defaultRenderStates
});

View file

@ -22,6 +22,8 @@
(function() {
Script.include("/~/system/libraries/controllers.js");
var ControllerDispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
var END_RADIUS = 0.005;
var dim = { x: END_RADIUS, y: END_RADIUS, z: END_RADIUS };
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -36,6 +38,7 @@
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -57,6 +60,7 @@
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -126,17 +130,6 @@
};
this.updateLaserPointer = function(controllerData) {
var RADIUS = 0.005;
var dim = { x: RADIUS, y: RADIUS, z: RADIUS };
if (this.mode === "full") {
this.fullEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd});
} else if (this.mode === "half") {
this.halfEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd});
}
LaserPointers.enableLaserPointer(this.laserPointer);
LaserPointers.setRenderState(this.laserPointer, this.mode);
};
@ -212,8 +205,6 @@
LaserPointers.removeLaserPointer(this.laserPointer);
};
this.halfEnd = halfEnd;
this.fullEnd = fullEnd;
this.laserPointer = LaserPointers.createLaserPointer({
joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND",
filter: RayPick.PICK_HUD,

View file

@ -18,6 +18,8 @@ Script.include("/~/system/libraries/controllers.js");
Script.include("/~/system/libraries/utils.js");
(function () {
var END_RADIUS = 0.005;
var dim = { x: END_RADIUS, y: END_RADIUS, z: END_RADIUS };
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -32,6 +34,7 @@ Script.include("/~/system/libraries/utils.js");
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -53,6 +56,7 @@ Script.include("/~/system/libraries/utils.js");
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -121,17 +125,6 @@ Script.include("/~/system/libraries/utils.js");
};
this.updateLaserPointer = function(controllerData) {
var RADIUS = 0.005;
var dim = { x: RADIUS, y: RADIUS, z: RADIUS };
if (this.mode === "full") {
this.fullEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd});
} else if (this.mode === "half") {
this.halfEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd});
}
LaserPointers.enableLaserPointer(this.laserPointer);
LaserPointers.setRenderState(this.laserPointer, this.mode);
};
@ -230,10 +223,6 @@ Script.include("/~/system/libraries/utils.js");
LaserPointers.removeLaserPointer(this.laserPointer);
};
this.halfEnd = halfEnd;
this.fullEnd = fullEnd;
this.laserPointer = LaserPointers.createLaserPointer({
joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND",
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,

View file

@ -144,6 +144,12 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
Entities.deleteAction(this.targetEntityID, this.actionID);
this.actionID = null;
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release',
grabbedEntity: this.targetEntityID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
this.targetEntityID = null;
};

View file

@ -138,6 +138,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release',
grabbedEntity: this.targetEntityID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
this.grabbing = false;
this.targetEntityID = null;
};

View file

@ -148,6 +148,12 @@ Script.include("/~/system/libraries/utils.js");
}
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release',
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
this.grabbedThingID = null;
};

View file

@ -17,6 +17,8 @@ Script.include("/~/system/libraries/controllers.js");
(function() {
var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js");
var END_RADIUS = 0.005;
var dim = { x: END_RADIUS, y: END_RADIUS, z: END_RADIUS };
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -31,6 +33,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -52,6 +55,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -171,17 +175,6 @@ Script.include("/~/system/libraries/controllers.js");
};
this.updateLaserPointer = function(controllerData) {
var RADIUS = 0.005;
var dim = { x: RADIUS, y: RADIUS, z: RADIUS };
if (this.mode === "full") {
this.fullEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd});
} else if (this.mode === "half") {
this.halfEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd});
}
LaserPointers.enableLaserPointer(this.laserPointer);
LaserPointers.setRenderState(this.laserPointer, this.mode);
};
@ -366,8 +359,6 @@ Script.include("/~/system/libraries/controllers.js");
LaserPointers.removeLaserPointer(this.laserPointer);
};
this.halfEnd = halfEnd;
this.fullEnd = fullEnd;
this.laserPointer = LaserPointers.createLaserPointer({
joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND",
filter: RayPick.PICK_OVERLAYS,

View file

@ -264,6 +264,8 @@ Script.include("/~/system/libraries/controllers.js");
if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) {
this.pointFinger(false);
this.hideStylus();
this.stylusTouchingTarget = false;
this.relinquishTouchFocus();
return false;
}

View file

@ -17,6 +17,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
var END_RADIUS = 0.005;
var dim = { x: END_RADIUS, y: END_RADIUS, z: END_RADIUS };
var halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
@ -31,6 +33,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var halfEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
@ -52,6 +55,7 @@ Script.include("/~/system/libraries/controllers.js");
};
var fullEnd = {
type: "sphere",
dimensions: dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
@ -366,17 +370,6 @@ Script.include("/~/system/libraries/controllers.js");
};
this.updateLaserPointer = function(controllerData) {
var RADIUS = 0.005;
var dim = { x: RADIUS, y: RADIUS, z: RADIUS };
if (this.mode === "full") {
fullEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: fullEnd});
} else if (this.mode === "half") {
halfEnd.dimensions = dim;
LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: halfEnd});
}
LaserPointers.enableLaserPointer(this.laserPointer);
LaserPointers.setRenderState(this.laserPointer, this.mode);
};

View file

@ -1476,10 +1476,19 @@ function onFileSaveChanged(filename) {
function onFileOpenChanged(filename) {
// disconnect the event, otherwise the requests will stack up
Window.openFileChanged.disconnect(onFileOpenChanged);
try {
// Not all calls to onFileOpenChanged() connect an event.
Window.openFileChanged.disconnect(onFileOpenChanged);
} catch (e) {
// Ignore.
}
var importURL = null;
if (filename !== "") {
importURL = "file:///" + filename;
importURL = filename;
if (!/^(http|https):\/\//.test(filename)) {
importURL = "file:///" + importURL;
}
}
if (importURL) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {

View file

@ -150,6 +150,23 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
parentJointIndex: -1
});
this.homeButtonHighlightID = Overlays.addOverlay("circle3d", {
name: "homeButtonHighlight",
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 },
localRotation: { x: 0, y: 1, z: 0, w: 0 },
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor },
solid: true,
outerRadius: 25 * tabletScaleFactor,
innerRadius: 20 * tabletScaleFactor,
ignoreIntersection: true,
alpha: 1.0,
color: { red: 255, green: 255, blue: 255 },
visible: visible,
drawInFront: false,
parentID: this.tabletEntityID,
parentJointIndex: -1
});
this.receive = function (channel, senderID, senderUUID, localOnly) {
if (_this.homeButtonID == senderID) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
@ -170,6 +187,23 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
}
};
this.myOnHoverEnterOverlay = function (overlayID, pointerEvent) {
_this.onHoverEnterOverlay(overlayID, pointerEvent);
};
Overlays.hoverEnterOverlay.connect(this.myOnHoverEnterOverlay);
this.myOnHoverLeaveOverlay = function (overlayID, pointerEvent) {
_this.onHoverLeaveOverlay(overlayID, pointerEvent);
};
Overlays.hoverLeaveOverlay.connect(this.myOnHoverLeaveOverlay);
this.myOnHoverOverOverlay = function (overlayID, pointerEvent) {
_this.onHoverOverOverlay(overlayID, pointerEvent);
};
Overlays.hoverOverOverlay.connect(this.myOnHoverOverOverlay);
this.state = "idle";
this.getRoot = function() {
@ -273,9 +307,14 @@ WebTablet.prototype.setWidth = function (width) {
};
WebTablet.prototype.destroy = function () {
Overlays.hoverEnterOverlay.disconnect(this.myOnHoverEnterOverlay);
Overlays.hoverLeaveOverlay.disconnect(this.myOnHoverLeaveOverlay);
Overlays.hoverOverOverlay.disconnect(this.myOnHoverOverOverlay);
Overlays.deleteOverlay(this.webOverlayID);
Overlays.deleteOverlay(this.tabletEntityID);
Overlays.deleteOverlay(this.homeButtonID);
Overlays.deleteOverlay(this.homeButtonHighlightID);
HMD.displayModeChanged.disconnect(this.myOnHmdChanged);
Controller.mousePressEvent.disconnect(this.myMousePressEvent);
@ -367,6 +406,24 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos
};
};
WebTablet.prototype.onHoverEnterOverlay = function (overlayID, pointerEvent) {
if (overlayID === this.homeButtonID) {
Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 1.0 });
}
}
WebTablet.prototype.onHoverOverOverlay = function (overlayID, pointerEvent) {
if (overlayID !== this.homeButtonID) {
Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 });
}
}
WebTablet.prototype.onHoverLeaveOverlay = function (overlayID, pointerEvent) {
if (overlayID === this.homeButtonID) {
Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 });
}
}
// compute position, rotation & parentJointIndex of the tablet
WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMouse, tabletProperties) {
if (HMD.active) {
@ -504,6 +561,22 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() {
}
};
WebTablet.prototype.handleHomeButtonHover = function(x, y) {
var pickRay = Camera.computePickRay(x, y);
var entityPickResults;
var homebuttonHovered = false;
entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID ||
entityPickResults.overlayID === this.tabletEntityID)) {
var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID], []);
if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) {
homebuttonHovered = true;
}
}
Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homebuttonHovered ? 1.0 : 0.0 });
}
WebTablet.prototype.mouseMoveEvent = function (event) {
if (this.dragging) {
this.currentMouse = {
@ -511,6 +584,8 @@ WebTablet.prototype.mouseMoveEvent = function (event) {
y: event.y
};
this.scheduleMouseMoveProcessor();
} else {
this.handleHomeButtonHover(event.x, event.y);
}
};
@ -537,6 +612,8 @@ WebTablet.prototype.mouseMoveProcessor = function () {
});
}
this.scheduleMouseMoveProcessor();
} else {
this.handleHomeButtonHover(this.currentMouse.x, this.currentMouse.y);
}
};

View file

@ -370,7 +370,7 @@ getTabletWidthFromSettings = function () {
resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) {
if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID) {
if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) {
return;
}
@ -413,4 +413,11 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0},
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}
});
Overlays.editOverlay(HMD.homeButtonHighlightID, {
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 },
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor },
outerRadius: 25 * tabletScaleFactor,
innerRadius: 20 * tabletScaleFactor
});
};

View file

@ -103,6 +103,7 @@
UIWebTablet.register();
HMD.tabletID = UIWebTablet.tabletEntityID;
HMD.homeButtonID = UIWebTablet.homeButtonID;
HMD.homeButtonHighlightID = UIWebTablet.homeButtonHighlightID;
HMD.tabletScreenID = UIWebTablet.webOverlayID;
HMD.displayModeChanged.connect(onHmdChanged);
MyAvatar.sensorToWorldScaleChanged.connect(onSensorToWorldScaleChanged);
@ -127,6 +128,7 @@
tabletProperties.visible = true;
Overlays.editOverlay(HMD.tabletID, tabletProperties);
Overlays.editOverlay(HMD.homeButtonID, { visible: true });
Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: true });
Overlays.editOverlay(HMD.tabletScreenID, { visible: true });
Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 });
updateTabletWidthFromSettings(true);
@ -147,6 +149,7 @@
Overlays.editOverlay(HMD.tabletID, { visible: false });
Overlays.editOverlay(HMD.homeButtonID, { visible: false });
Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: false });
Overlays.editOverlay(HMD.tabletScreenID, { visible: false });
Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 });
}
@ -167,6 +170,7 @@
UIWebTablet = null;
HMD.tabletID = null;
HMD.homeButtonID = null;
HMD.homeButtonHighlightID = null;
HMD.tabletScreenID = null;
} else if (debugTablet) {
print("TABLET closeTabletUI, UIWebTablet is null");
@ -319,6 +323,7 @@
Overlays.deleteOverlay(tabletID);
HMD.tabletID = null;
HMD.homeButtonID = null;
HMD.homeButtonHighlightID = null;
HMD.tabletScreenID = null;
});
}()); // END LOCAL_SCOPE

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global CreatePalette: true, App, Feedback, History, UIT */
/* global CreatePalette:true, App, Feedback, History, Preload, UIT */
CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Tool menu displayed on top of forearm.
@ -332,6 +332,11 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
}
function getAssetURLs() {
return Preload.findURLs([PALETTE_HEADER_HEADING_PROPERTIES, PALETTE_HEADER_BAR_PROPERTIES, PALETTE_TITLE_PROPERTIES,
PALETTE_ITEMS]);
}
function setHand(hand) {
// Assumes UI is not displaying.
var NUMBER_OF_HANDS = 2;
@ -533,6 +538,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
}
return {
assetURLs: getAssetURLs,
setHand: setHand,
overlayIDs: getOverlayIDs,
setVisible: setVisible,

View file

@ -0,0 +1,175 @@
//
// preload.js
//
// Created by David Rowe on 11 Oct 2017.
// Copyright 2017 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
//
/* global Preload:true, App */
Preload = (function () {
// Provide facility to preload asset files so that they are in disk cache.
// Global object.
"use strict";
var loadTimer = null,
urlsToLoad = [],
nextURLToLoad = 0,
LOAD_INTERVAL = 50, // ms
overlays = [],
deleteTimer = null,
DELETE_INTERVAL = LOAD_INTERVAL; // ms
function findURLs(items) {
var urls = [],
i,
length;
function findURLsInObject(item) {
var property;
for (property in item) {
if (item.hasOwnProperty(property)) {
if (property === "url" || property === "imageURL" || property === "imageOverlayURL") {
if (item[property]) {
urls.push(item[property]);
}
} else if (typeof item[property] === "object") {
findURLsInObject(item[property]);
}
}
}
}
for (i = 0, length = items.length; i < length; i++) {
switch (typeof items[i]) {
case "string":
urls.push(items[i]);
break;
case "object":
findURLsInObject(items[i]);
break;
default:
App.log("ERROR: Cannot find URL in item type " + (typeof items[i]));
}
}
return urls;
}
function deleteOverlay() {
if (overlays.length === 0) { // Just in case.
deleteTimer = null;
return;
}
Overlays.deleteOverlay(overlays[0]);
overlays.shift();
if (overlays.length > 0) {
deleteTimer = Script.setTimeout(deleteOverlay, DELETE_INTERVAL);
} else {
deleteTimer = null;
}
}
function loadNextURL() {
function loadURL(url) {
var DOMAIN_CORNER = { x: -16382, y: -16382, z: -16382 }, // Near but not right on domain corner.
DUMMY_OVERLAY_PROPERTIES = {
fbx: {
overlay: "model",
dimensions: { x: 0.001, y: 0.001, z: 0.001 },
position: DOMAIN_CORNER,
ignoreRayIntersection: false,
alpha: 0.0,
visible: false
},
svg: {
overlay: "image3d",
scale: 0.001,
position: DOMAIN_CORNER,
ignoreRayIntersection: true,
alpha: 0.0,
visible: false
},
png: {
overlay: "image3d",
scale: 0.001,
position: DOMAIN_CORNER,
ignoreRayIntersection: true,
alpha: 0.0,
visible: false
}
},
fileType,
properties;
fileType = url.slice(-3);
if (DUMMY_OVERLAY_PROPERTIES.hasOwnProperty(fileType)) {
properties = Object.clone(DUMMY_OVERLAY_PROPERTIES[fileType]);
properties.url = url;
overlays.push(Overlays.addOverlay(properties.overlay, properties));
if (deleteTimer === null) {
// Can't delete overlay straight away otherwise asset load is abandoned.
deleteTimer = Script.setTimeout(deleteOverlay, DELETE_INTERVAL);
}
} else {
App.log("ERROR: Cannot preload asset " + url);
}
}
// Find next URL that hasn't already been loaded;
while (nextURLToLoad < urlsToLoad.length && urlsToLoad.indexOf(urlsToLoad[nextURLToLoad]) < nextURLToLoad) {
nextURLToLoad += 1;
}
// Load the URL if there's one to load.
if (nextURLToLoad < urlsToLoad.length) {
// Load the URL.
loadURL(urlsToLoad[nextURLToLoad]);
nextURLToLoad += 1;
// Load the next, if any, after a short delay.
loadTimer = Script.setTimeout(loadNextURL, LOAD_INTERVAL);
} else {
loadTimer = null;
}
}
function load(urls) {
urlsToLoad = urlsToLoad.concat(urls);
if (loadTimer === null) {
loadNextURL();
}
}
function tearDown() {
var i,
length;
if (loadTimer) {
Script.clearTimeout(loadTimer);
}
if (deleteTimer) {
Script.clearTimeout(deleteTimer);
for (i = 0, length = overlays.length; i < length; i++) {
Overlays.deleteOverlay(overlays[i]);
}
}
}
Script.scriptEnding.connect(tearDown);
return {
findURLs: findURLs,
load: load
};
}());

View file

@ -72,6 +72,10 @@ ToolIcon = function (side) {
return new ToolIcon();
}
function getAssetURLs() {
return [MODEL_PROPERTIES.url];
}
function setHand(side) {
// Assumes UI is not displaying.
if (side === LEFT_HAND) {
@ -154,6 +158,7 @@ ToolIcon = function (side) {
}
return {
assetURLs: getAssetURLs,
setHand: setHand,
display: display,
clear: clear,

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global ToolsMenu: true, App, Feedback, UIT */
/* global ToolsMenu:true, App, Feedback, Preload, UIT */
ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Tool menu displayed on top of forearm.
@ -2042,6 +2042,12 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
return new ToolsMenu();
}
function getAssetURLs() {
return Preload.findURLs([MENU_HEADER_HEADING_PROPERTIES, MENU_HEADER_BAR_PROPERTIES, MENU_HEADER_BACK_PROPERTIES,
MENU_HEADER_TITLE_PROPERTIES, MENU_HEADER_TITLE_BACK_URL, MENU_HEADER_ICON_PROPERTIES, UI_ELEMENTS,
PICKLIST_UP_ARROW, PICKLIST_DOWN_ARROW, OPTONS_PANELS, MENU_ITEMS, FOOTER_ITEMS]);
}
controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand();
function setHand(hand) {
@ -3672,6 +3678,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
}
return {
assetURLs: getAssetURLs,
COLOR_TOOL: COLOR_TOOL,
SCALE_TOOL: SCALE_TOOL,
CLONE_TOOL: CLONE_TOOL,

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Feedback, History */
/* global Feedback, History, Preload */
(function () {
@ -87,6 +87,7 @@
Script.include("./modules/highlights.js");
Script.include("./modules/history.js");
Script.include("./modules/laser.js");
Script.include("./modules/preload.js");
Script.include("./modules/selection.js");
Script.include("./modules/toolIcon.js");
Script.include("./modules/toolsMenu.js");
@ -235,8 +236,12 @@
}
toolIcon = new ToolIcon(otherHand(side));
toolsMenu = new ToolsMenu(side, leftInputs, rightInputs, uiCommandCallback);
createPalette = new CreatePalette(side, leftInputs, rightInputs, uiCommandCallback);
toolsMenu = new ToolsMenu(side, leftInputs, rightInputs, uiCommandCallback);
Preload.load(toolIcon.assetURLs());
Preload.load(createPalette.assetURLs());
Preload.load(toolsMenu.assetURLs());
getIntersection = side === LEFT_HAND ? rightInputs.intersection : leftInputs.intersection;